Программирование графических процессоров с использованием Direct3D и HLSL

Математические основы компьютерной графики

Для того чтобы отображать графические объекты на дисплее нужно иметь некий инструмент, позволяющий легко и просто описывать эти объекты на языке математики. Положение точек на плоскости очень удобно описывать с помощью декартовой системы координат. Чтобы создать декартову систему координат нужно провести две прямые неколлинеарные линии, которые называют осями. Пусть они пересекаются в точке O, которую называют началом координат. Выберем на построенных осях единицу измерения. Тогда положение любой точки плоскости можно описать через координаты этой точки, которые представляют собой расстояния от начала координат до проекций точки на соответствующие оси координат. Проекцией точки на координатную ось называется точка пересечения прямой, проходящей через заданную точку и параллельной другой оси координат. Вообще введенные оси координат могут располагаться под произвольным углом (рис. 1.1).
Математические основы компьютерной графики
Рис. 1.1. 
Однако, на практике удобно пользоваться системой координат со взаимно перпендикулярными осями. Такая система координат называется ортогональной. Оси координат имеют названия; горизонтальная ось называется осью абсцисс (Ox), вертикальная – осью ординат (Oy). Таким образом, точка на плоскости представляется двумя своими координатами, что записывается в виде двумерного вектора P=(x,y).
Математический аппарат описания точек на плоскости с помощью декартовой системы координат идеально подходит для выполнения различных аффинных преобразований над точками (сдвиг, масштабирование, вращение).
Точку P(x,y), заданную на плоскости можно перенести (сдвинуть) в новую позицию путем добавления к координатам этой точки констант переноса. Для произвольной точки P=(x,y), которая перемещается в новую точку P'=(x',y'), сдвигаясь на Tx единиц параллельно оси абсцисс и на Ty единиц параллельно оси ординат, можно записать следующие выражения: x'=x+Tx, y'=y+Ty. Так, например, точка с координатами P(1,2) смещаясь на расстояние (5,7) преобразуется в точку P'(6,9). Определяя точку и перенос как вектор-строки P=(x,y), P'=(x',y') и T=(Tx,Ty) можно записать преобразование переноса (сдвига) в векторной форме: (x',y')=(x,y)+(Tx,Ty) или P'=P+T. Преобразованию можно подвергнуть не только одни точки. Геометрический объект можно переместить, применив к каждой его точке преобразование переноса. Так, если в описании объекта имеются отрезки прямой, то достаточно применить преобразование к концам отрезка и затем провести прямую линию между двумя преобразованными точками. Это правило справедливо и для операций масштабирования и поворота. На рис. 1.2 представлен результат действия на треугольник операции переноса на расстояние (2,-1).
Математические основы компьютерной графики
Рис. 1.2. 
Точки можно подвергнуть операции масштабирования (растяжения или сжатия) в Sx раз вдоль оси абсцисс и в Sy раз вдоль оси ординат. Полученные в результате новые точки будут выражаться как: x'=x*Sx;y'=y*Sy. Определив S как Математические основы компьютерной графики данные выражения можно записать в матричной форме: Математические основы компьютерной графики или P'=P*S. На рис. 1.3 показан треугольник, промасштабированный с коэффициентами 0,5 по оси абсцисс и коэффициентом 2 вдоль оси ординат.
Математические основы компьютерной графики
Рис. 1.3. 
Следует отметить, что операция масштабирования производится относительно начала координат. В результате преобразования объект может стать меньше/больше в размерах и ближе/дальше от начала координат. Пропорции объекта также могут измениться при масштабировании с различными коэффициентами:SxМатематические основы компьютерной графикиSy. Для сохранения пропорций необходимо, чтобы масштабные коэффициенты были равны:Sx=Sy.
Точка плоскости P=(x,y) может быть повернута на произвольный угол Математические основы компьютерной графики относительно начала координат и перейдет в новую точку P'=(x',y') (рис. 1.4)
Математические основы компьютерной графики
Рис. 1.4. 
Выведем формулы для пересчета точки (x,y) в точку (x',y'). Обозначим расстояние от начала координат до точки P(x,y) через ?. Очевидно, что расстояние от начала координат до точки P'(x',y') также будет ?. Пусть Q и Q' - проекции точек P и P' соответственно на ось абсцисс. Тогда из прямоугольного треугольника OP'Q' и тригонометрических определений синуса и косинуса имеем:
Математические основы компьютерной графики
Домножим правую и левую части уравнений на ?.
Математические основы компьютерной графики
Используя простейшие тригонометрические свойства прямоугольного треугольника OPQ, следует заметить, что ?cos?=x, а ?sin?=y. Таким образом, формула "перевода" точки P(x,y) в точку P'(x',y') поворотом на угол Математические основы компьютерной графики относительно начала координат будет:
Математические основы компьютерной графики
В матричном виде преобразование вращения будет выглядеть так:
Математические основы компьютерной графики
Так треугольник с координатами вершин (20,0),(60,0),(40,100) после поворота на угол 45 градусов по часовой стрелке относительно начала координат (Математические основы компьютерной графики=-45°) будет иметь новые значения координат вершин:Математические основы компьютерной графики.
Точка плоскости P(x,y) может быть легко отражена относительно прямых y=0, x=0, y=x следующим образом. Отражение относительно прямой y=0 (ось абсцисс) может быть получено с использованием матрицы Математические основы компьютерной графики . Так, например, точка P=(2,3) при таком отражении преобразуется в точку Математические основы компьютерной графики (рис. 1.5).
Подобным образом матрица отражения относительно прямой x=0 (ось ординат) будет иметь вид Математические основы компьютерной графики . Точка P=(2,3) при отражении относительно оси ординат преобразуется в точку Математические основы компьютерной графики (рис. 1.5).
Отражение относительно прямой y=x осуществляется с помощью матрицы Математические основы компьютерной графики . Точка P=(2,3) в результате такого отражения преобразуется в точку Математические основы компьютерной графики (рис. 1.5).
Математические основы компьютерной графики
Рис. 1.5. 
Рассмотренные выше аффинные преобразования переноса, масштабирования, вращения и отражения можно записать в матричной форме следующим образом: P'=P+T, P'=P*S, P'=P*R, P'=P*M, где P' - координаты преобразованной точки, P - координаты исходной точки, T - вектор сдвига (translate), S - матрица масштабирования (scale), R - матрица вращения (rotate), M - матрица отражения (mirror). К сожалению, операция переноса (сдвига) реализуется отдельно (с помощью сложения) от масштабирования, поворота и отражения (с помощью умножения). Тем не менее, существует возможность, чтобы все эти элементарные преобразования (перенос, масштабирование, вращение, отражение) можно было реализовать с помощью только операций умножения матриц. Данная возможность реализуется с помощью так называемых однородных координат точки.
Однородное представление двумерной точки (x,y) в общем случае имеет вид (wx wy w), где w - любой ненулевой скаляр, иногда называемый множителем. При этом если для точки задано ее представление в однородных координатах P(x y w), то найти ее двумерные координаты можно поделив первые две на скалярный множитель (x/w y/w). Вообще двумерное представление точки (x y w) есть ее проекция на плоскость w=1 (рис. 1.6).
Математические основы компьютерной графики
Рис. 1.6. 
Теперь точки плоскости можно описывать трехэлементным вектором, а матрицы преобразования должны иметь размер 3х3. В общем случае преобразование точки (x,y) в новую точку (x',y') можно представить следующим образом Математические основы компьютерной графики.
Уравнения переноса (сдвига), масштабирования и вращения записываются в виде матриц преобразования однородных координат следующим образом:
Математические основы компьютерной графикиМатематические основы компьютерной графикиМатематические основы компьютерной графики
где Tx,Ty - величины сдвига, Sx,Sy - масштабные множители, Математические основы компьютерной графики - угол поворота.
Преимущество такого подхода (матричных формул) заключается в том, что совмещение последовательных элементарных преобразований при этом значительно упрощается. Рассмотрим следующую последовательность преобразований: масштабирование исходной точки P(x,y) при масштабных коэффициентах Sx и Sy, а затем смещение ее (после масштабирования) на Tx и Ty. Запишем преобразования масштабирования и переноса (сдвига) через однородные координаты точки:Математические основы компьютерной графики Математические основы компьютерной графики
Подставим первое уравнение во второе:Математические основы компьютерной графики Две квадратные матрицы независимы от преобразуемой точки (x,y) и поэтому их можно перемножить между собой.
В результате получим Математические основы компьютерной графики Таким образом, результирующая матрица, полученная произведением двух исходных матриц преобразования, представляет собой совмещение элементарных преобразований. Независимо от количества элементарных преобразований в последовательности, можно всегда произвести совмещение так, чтобы только одна матрица 3х3 представляла всю последовательность преобразований. Следует заметить, что если Математические основы компьютерной графики и Математические основы компьютерной графики представляют собой матрицы элементарных преобразований, то существует две возможные композиции: Математические основы компьютерной графики и Математические основы компьютерной графики. Однако, результаты таких преобразований будут различны, в силу того, что произведение матриц не является коммутативной операцией. Если геометрический объект состоит из большого колич ества вершин (точек), то с вычислительной точки зрения гораздо более эффективнее и проще применять композитную (результирующую) матрицу преобразования вместо того, чтобы последовательно использовать ("умножать на") одну за другой элементарные матрицы.
До сих пор мы рассматривали преобразования как перевод множества точек, принадлежащих объекту, в некоторое другое множество точек, причем оба эти множества описаны в одной и той же системе координат. Другими словами система координат у нас оставалась неизменной, а сам объект преобразовывался относительно начала координат. Эквивалентным способом описания преобразования является смена системы координат. Такой подход оказывается полезным и удобным, когда необходимо собрать вместе много объектов, каждый из которых описан в своей собственной локальной системе координат, и выразить (пересчитать) их координаты в одной глобальной (мировой) системе координат. Например, точка на рис. 1.7 описана в четырех системах координат, и имеет соответствующие координаты: (11,10), (8,8), (12,10), (3,3)
Математические основы компьютерной графики
Рис. 1.7. 
Преобразование из системы координат 1 в систему координат 2 есть Математические основы компьютерной графики; из 2 в 3 есть Математические основы компьютерной графики; из 3 в 4 есть Математические основы компьютерной графики. В общем случае преобразование Математические основы компьютерной графики переводит оси системы координат j в оси системы координат i. Если Математические основы компьютерной графики - точка, координаты которой заданы в системе координат j, то будет справедлива запись Математические основы компьютерной графики. Так, например, в рассматриваемом случае Математические основы компьютерной графики записывается в однородных координатах Математические основы компьютерной графики, а Математические основы компьютерной графики. И преобразование будет иметь вид: Математические основы компьютерной графики Преобразование Математические основы компьютерной графики имеет обратное Математические основы компьютерной графики - преобразование из системы координат 2 в систему 1, причем Математические основы компьютерной графики. В рассматриваемом случае Математические основы компьютерной графики Нетрудно проверить, что Математические основы компьютерной графики (единичная матрица). Кроме того будет справедливо и такое выражение Математические основы компьютерной графики. Другими словами, преобразование из системы координат 1 в систему координат 3 есть произведение двух матриц, первая из которых описывает преобразование из системы 1 в систему 2, а вторая – из системы 2 в систему 3.
Для введения трехмерной декартовой системы координат проведем три направленные взаимно перпендикулярные прямые линии, называемые осями, так чтобы они пересекались в одной точке – начале координат. Выберем на осях единицу измерения. Тогда положение любой точки пространства можно описать через координаты этой точки, которые представляют собой расстояния от начала координат до проекций точки на соответствующие оси. Такая система координат называется ортогональной. Таким образом, положение точки P в пространстве описывается ее координатами: P=(x,y,z). Взаимное расположение координатных осей в ортогональной системе трехмерного пространства может быть двух видов. При добавлении третьей оси к двумерной системе координат ось Oz можно направить как от наблюдателя в плоскость листа, так и от плоскости листа к наблюдателю.
Математические основы компьютерной графики
Рис. 1.8. 
В первом случае систему координат принято называть левосторонней, во втором – правосторонней. Известен способ определения типа системы по ладоням. Так для левой ладони большой (ось Y), указательный (ось Z) и средний (ось X) пальцы образуют левую тройку ортогональных векторов.
В трехмерном пространстве значительно возрастает разнообразие геометрических объектов. При работе на двумерной плоскости мы рассматривали отрезки, плоские кривые и многоугольники. При переходе в трехмерное пространство это многообразие примитивов можно рассматривать в разных плоскостях, а также здесь появляются пространственные кривые: ?(t)=[x(t),t(t),z(t)], t?[a,b]. Помимо всего прочего в трехмерном пространстве присутствуют пространственные объекты – участки криволинейных поверхностей и объемные тела – параллелепипеды, эллипсы, тетраэдры и др.
При работе в трехмерном пространстве возникает проблема описания формы объектов. На практике получили широкое распространение три основных типа моделей трехмерных объектов: описание объекта поверхностями, сплошными телами и с помощью проволочной сетки. При первом подходе объект представляется в виде тонких поверхностей, под которым находится пустое незаполненное пространство. Примером такого объекта может выступать неразбитая скорлупа совершенно пустого внутри яйца. Поверхность объекта может быть описана различными математическими моделями. Поверхности, заданные в виде x=x(u,v),y=y(u,v),z=z(u,v), где u,v - параметры, изменяющиеся в заданных пределах, относятся к классу параметрических. Для одной фиксированной пары значений u,v можно вычислить положение только одной точки поверхности. Для полного представления всей поверхности необходимо с определенным шагом перебрать множество пар u,v из диапазона их изменений, вычисляя для каждой пары значение XYZ в трехмерном пространстве. Очень широкое распространение получили параметрические бикубические поверхности, с помощью которых достигается непрерывность составной функции и ее первых производных (функция, составленная из нескольких смежных бикубических участков, будет обладать непрерывностью и гладкостью в местах стыковки). Основным преимуществом параметрического описания является возможность построения объекта с очень сложной и замысловатой формой. Недостатком такого способа описания являются большие вычислительные затраты при построении поверхностей. Частным случаем параметрических поверхностей являются поверхности первого порядка. Из таких поверхностей можно составить описание формы объекта типа полигонального поля. Такими полями называют серию смежных многоугольников, не имеющих разрывов между собой. Каждое ребро такого поля является общим для смежных многоугольников. В результате чего составная функция, описывающая поверхность, обладает непрерывностью, а производная имеет разрывы в местах стыка
участков поверхностей. В настоящее время полигональный способ описания трехмерных объектов является одним из самых распространенных и востребованных. Так, например, производительность современных графических процессоров (видеокарт) определяется количеством выводимых полигонов в единицу времени, как правило, в секунду.
Еще один способ описания поверхностей, который следует упомянуть, заключается в представлении формы объекта множеством отдельных точек, принадлежащих этой поверхности. Теоретически при бесконечном увеличении числа точек такая модель обеспечивает непрерывную форму описания. Точки, используемые для описания, должны располагаться достаточно близко друг к другу, чтобы можно было воспринять поверхность без грубых потерь и искажений. Поточечное описание поверхностей применяют в тех случаях, когда поверхность очень сложна, не обладает нужной гладкостью, а детальное представление многочисленных геометрических особенностей важно для практики.
Описание объекта сплошными геометрическими конструктивами (твердотельное моделирование) заключается в представлении сложного объекта в виде объединения простых объемных примитивов. Обычно такие примитивы включают кубы, цилиндры, конусы, эллипсоиды и другие подобные формы. Булевы операции над примитивами позволяют достигать объединения, вычитания и выделения общих частей примитивов. Структуры данных модели этого вида идентичны бинарному дереву, причем узлы (нетерминальные вершины) дерева являются операторами над примитивами, а листья – примитивами.
Следует также отметить метод описания объекта с помощью проволочной сетки (wire-frame), суть которого заключается в представлении поверхности серией пересекающихся линий, принадлежащих поверхности объекта. Как правило, в качестве таких линий принято использовать отрезки прямых. Достоинством проволочного представления является простой и эффективный способ построения объектов.
Для наилучшего восприятия формы объекта необходимо иметь его представление в трехмерном пространстве. Как правило, наглядное представление об объекте можно получить с помощью выполнения операций вращения и переноса, а также путем построения его проекций. Как и двумерном случае, существует три основных преобразования в трехмерном пространстве: перенос (изменение положения), изменение масштаба и вращение.
Преобразование перемещения точки трехмерного пространства P=(x,y,z) в новую точку P'=(x',y',z') можно записать следующим образом: x'=x+Tx, y'=y+Ty, z'=z+Tz, где Tx,Ty,Tz - величины перемещения в направлениях x,y,z соответственно. Определяя точку и операцию переноса как вектор-строку P=(x,y,z), P'=(x',y',z'),T=(Tx,Ty,Tz), преобразование сдвига можно записать в векторной форме: (x',y',z')=(x,y,z)+(Tx,Ty,Tz) или P'=P+T.
Точку трехмерного пространства P=(x,y,z) можно подвергнуть операции масштабирования (растяжения или сжатия) в Sx раз по оси абсцисс, в Sy раз по оси ординат и в Sz раз по оси аппликат. Полученная в результате преобразованная точка P'=(x',y',z') будет выражаться как: x'=x*Sx,y'=y*Sy,z'=z*Sz. Определив S как матрицу Математические основы компьютерной графики выражения для масштабирования можно переписать в матричной форме: Математические основы компьютерной графики или P'=P*S. Как и в двумерном случае операция масштабирования производится относительно начала координат. Поэтому если масштабируемые множители Sx,Sy,Sz>1, то преобр
азуемая точка отдаляется от начала координат, если же Sx,Sy,Sz<1 то точка приблизится к началу координат.
Трехмерные преобразования вращения являются более сложными, чем их двумерные аналоги. В данном случае необходимо дополнительно задать ось вращения. Рассмотрим сначала простейшие случаи, когда ось вращения совпадает с одной из координатных осей.
Найдем матрицу поворота вокруг оси OZ на угол ?. Будем записывать матрицу преобразования для левосторонней системы координат. Следует отметить, что в левосторонней системе координат положительными будут повороты, выполняемые по часовой стрелке, если смотреть с конца положительной полуоси в направлении начала координат (рис. 1.9).
Математические основы компьютерной графики
Рис. 1.9. 
В данном случае ось поворота перпендикулярна к плоскости рисунка, и поскольку мы используем левостороннюю систему координат, то вращение вокруг оси OZ сводится к повороту точки на плоскости XOY на угол ?. При этом координата z точки вращения не изменяется. Таким образом, формулу поворота точки (x,y,z) вокруг оси OZ на угол ? можно записать следующим образом:
Математические основы компьютерной графики или в матричной форме Математические основы компьютерной графики
Изменим теперь положение координатных осей левосторонней системы координат таким образом, чтобы ось OY была направлена в плоскость рисунка. Тогда положительная полуось OZ будет направлена горизонтально вправо, а положительная полуось OX - вертикально вверх (рис. 1.10).
Математические основы компьютерной графики
Рис. 1.10. 
Получить формулу вращения точки вокруг оси OY на угол ? можно заменив x на z, y на x в формуле двумерного поворота. При этом координата точки y при таком вращении не изменяется. В результате чего формула вращения точки (x,y,z) вокруг оси OY на угол ? будет иметь следующий вид: Математические основы компьютерной графики или в матричной форме Математические основы компьютерной графики
Аналогично поступаем с осью вращения OX. Изменим положение координатных осей так, чтобы ось OX была направлена в плоскость рисунка, ось OY - горизонтально вправо, ось OZ - вертикально вверх (рис. 1.11).
Математические основы компьютерной графики
Рис. 1.11. 
Заменив в формуле двумерного поворота y на z, x на y, получим формулу вращения точки (x,y,z) вокруг оси OX на угол Математические основы компьютерной графики: Математические основы компьютерной графики или в матричной форме Математические основы компьютерной графики
Способ двумерного плоского вращения вокруг произвольной точки может быть обобщен на случай вращения вокруг произвольной оси трехмерного пространства. Пусть произвольная ось вращения задается вектором Математические основы компьютерной графики, причем Математические основы компьютерной графики - точка, определяющая начало вектора, а Математические основы компьютерной графики - конец вектора (рис. 1.12)
Математические основы компьютерной графики
Рис. 1.12. 
Вращение вокруг задаваемой оси (вектора Математические основы компьютерной графики) на угол ? выполняется в несколько этапов:
  • Перенос вектора Математические основы компьютерной графики так, чтобы начало вектора (точка Математические основы компьютерной графики) совпала с началом системы координат. Это осуществляется с помощью операции сдвига T(-a,-b,-c);
  • Поворот вокруг оси OY на угол ? так, чтобы вектор (m,l,n) оказался в плоскости OYZ: Математические основы компьютерной графики;
  • Поворот вокруг оси OX на угол Математические основы компьютерной графики так, чтобы вектор (m',l',n') совпал с осью OZ: Математические основы компьютерной графики;
  • Поворот вокруг оси OZ на заданный угол ?: Rx(?);
  • Выполнение преобразования, обратного, произведенному на шаге 3. Т.е. поворот вокруг оси OX на угол -Математические основы компьютерной графики;
  • Выполнение преобразования, обратного, произведенному на шаге 2. Т.е. поворот вокруг оси OY на угол -?;
  • Выполнение преобразования, обратного, произведенному на шаге 1. Т.е. сдвиг на вектор (a,b,c):T(a,b,c)

  • Данный алгоритм вращения вокруг произвольной оси можно записать с помощью произведения серии элементарных матриц: Математические основы компьютерной графики, где V - исходная точка, V' - точка после поворота.
    Остается определить чему равны углы поворотов Математические основы компьютерной графики и ? (рис. 1.13).
    Математические основы компьютерной графики
    Рис. 1.13. 
    Из простых тригонометрических соотношений можно получить следующие формулы: Математические основы компьютерной графики
    Как видно, операции трехмерного масштабирования и вращения могут быть реализованы с помощью умножения вектор-строки (точки) на матрицу преобразования. Операция же сдвига реализуется через сложение двух вектор-строк. Аналогично тому, как все двумерные преобразования (сдвиг, масштабирование и вращение) описываются матрицами размером 3х3 (через однородные координаты), трехмерные преобразования могут быть представлены в виде матриц размером 4х4. И тогда точка трехмерного пространства (x,y,z) записывается в однородных координатах как Wx,Wy,Wz,W, где WМатематические основы компьютерной графики0. Если WМатематические основы компьютерной графики1, то для получения трехмерных декартовых координат точки (x,y,z) первые три однородные координаты нужно разделить на W. Отсюда следует, что две точки Математические основы компьютерной графики и Математические основы компьютерной графики в пространстве однородных координат описывают одну и ту же точку трехмерного пространства в том и только том случае, когда Математические основы компьютерной графики для любой константы c не равной нулю. Таким образом, преобразование точки трехмерного пространства P=(x,y,z) в новую точку P'=(x',y',z') с использованием однородных координат можно записать как: Математические основы компьютерной графики
    Уравнения трехмерного поворота, масштабирования и вращения записываются в виде матриц преобразования однородных координат следующим образом: Математические основы компьютерной графики Математические основы компьютерной графики Математические основы компьютерной графики Математические основы компьютерной графики Математические основы компьютерной графики
    где Tx,Ty,Tz - величины сдвига по осям OX, OY, OZ соответственно, Sx,Sy,Sz - масштабные множители по OX,OY,OZ соответственно, Математические основы компьютерной графики - матрицы вращения вокруг осей OX,OY,OZ на углы Математические основы компьютерной графики,?, ? соответственно.
    Как и в двумерном случае, матричный подход позволяет совместить два или более элементарных преобразования в одно. Таким образом, последовательное применение двух преобразований Математические основы компьютерной графики и Математические основы компьютерной графики может быть заменено применением одного преобразования T, причем матрица T будет равна произведению матриц преобразований Математические основы компьютерной графики и Математические основы компьютерной графики. Это легко можно увидеть на простом примере. Пусть точка (x,y,z) трансформируется в точку (x',y',z') с помощью преобразования Математические основы компьютерной графики: Математические основы компьютерной графики. Применяя затем преобразование Математические основы компьютерной графики к точке (x',y',z'), получим точку Математические основы компьютерной графики . Теперь подставляя первое выражение во второе, получим:
    Математические основы компьютерной графики. Причем порядок применения преобразований должен быть сохранен при перемножении соответствующих матриц.
    Процесс вывода трехмерной графической информации по существу является более сложным, чем соответствующий двумерный процесс. Сложность, характерная для трехмерного случая, обуславливается тем, что поверхность вывода не имеет графического третьего измерения. Такое несоответствие между пространственными объектами и плоскими изображениями устраняется путем введения проекций, которые отображают трехмерные объекты на двумерной проекционной картинной плоскости. В процессе вывода трехмерной графической информации мы задаем видимый объем в мировом пространстве, проекцию на картинную плоскость и поле вывода на видовой поверхности. В общем случае объекты, определенные в трехмерном мировом пространстве, отсекаются по границам трехмерного видимого объема и после этого проецируются. То, что попадает в пределы окна, которое само является проекцией видимого объема на картинную плоскость, затем преобразуется в поле вывода и отображается на графическом устройстве. В общем случае операция проекции преобразует точки, зада нные в системе координат размерности n, в точки системы координат размерности меньшей, чем n. В нашем случае точка трехмерного пространства отображается в двумерное пространство. Проекция трехмерного объекта строится при помощи прямых проецирующих лучей, которые называются проекторами и которые выходят из центра проекции, проходят через каждую точку объекта и, пересекая картинную плоскость, образуют проекцию. На рис. 1.14 представлены две различные проекции одного и того же отрезка и проекторы, проходящие через его конечные точки.
    Математические основы компьютерной графики
    Рис. 1.14. 
    Определенный таким образом класс проекций известен под названием плоских геометрических проекций, т.к. проецирование осуществляется на плоскость, а не на искривленную поверхность и в качестве проекторов используют прямые линии. Плоские геометрические проекции можно подразделить на два основных класса: центральные (перспективные) и параллельные (ортогональные). Различие между ними определяется соотношением между центром проекции и проекционной плоскостью. Так, если расстояние между ними, конечно, то проекция будет центральной, если же оно бесконечно, то – параллельной. При описании центральной проекции мы явно задаем ее центр проекции, в то время как для параллельной проекции мы указываем лишь направление проецирования. Центр проекции порождает визуальный эффект, аналогичный тому, к которому приводят фотографические системы и используется в случаях, когда желательно достичь некоторой степени реализма. Следует заметить, что размер центральной проекции объекта изменяется обратно пропорционально расстоянию от центра проекции до объекта. Параллельная проекция порождает менее реалистичное изображение, т.к. отсутствует перспективное "укорачивание" объекта. Проекция фиксирует истинные размеры объекта, и параллельные линии остаются параллельными.
    В общем случае задача получения центральной проекции заключается в том, чтобы определить проекцию точки объекта, расположенную в произвольном месте трехмерного пространства, на некоторую плоскость в этом же пространстве, называемую картинной. Нахождение центральной проекции является частным случаем задачи определения пересечения луча L с плоскостью Математические основы компьютерной графики в трехмерном пространстве (рис. 1.15)
    Математические основы компьютерной графики
    Рис. 1.15. 
    В машинной графике задача вычисления центральной проекции, как правило, сильно упрощена. В данном случае центр проекции, который также называют точкой зрения, находится на одной из осей системы координат, картинная (проекционная) плоскость перпендикулярна оптической оси. Как правило, точку зрения (центр проекции) располагают на оси OZ, тогда картинная плоскость будет параллельна плоскости OXY системы координат (рис. 1.16).
    Математические основы компьютерной графики
    Рис. 1.16. 
    В нашем случае точка C=(0,0,c) - центр проекции (положение наблюдателя), плоскость z=0 - картинная плоскость. Пусть точка P=(x,y,z) имеет проекцию P'=(x',y',0). Рассмотрим два подобных треугольника CPQ и CP'Q', и запишем отношение катетов: Математические основы компьютерной графики. Рассмотрим два других подобных треугольника CQ'O и CQB, и запишем отношения катетов для них: Математические основы компьютерной графики. С другой стороны имеем: Математические основы компьютерной графики. Так как OQ'=x', BQ=x, P'Q'=y', PQ=y имеем Математические основы компьютерной графики или после преобразований Математические основы компьютерной графики
    Если теперь cМатематические основы компьютерной графики, то получим формулу параллельной проекции:Математические основы компьютерной графики.
    Следующим шагом необходимо спроецированное изображение перевести в координаты экрана. Это можно проделать следующим образом: Математические основы компьютерной графики где Математические основы компьютерной графики - середина экрана, l - количество пикселей в единице.
    Существует связь однородных координат с операцией центральной и параллельной проекциями, которая может быть выражена так: Математические основы компьютерной графики.
    Для перехода от однородных координат к обычным, необходимо разделить все компоненты точки на четвертую координату: Математические основы компьютерной графики.
    Для параллельной проекции матрица преобразования будет иметь вид: Математические основы компьютерной графики.
    Таким образом, шаг проецирования можно описать в терминах матричной операции умножения. В результате этого мы можем объединить вместе операции преобразования объекта (сдвиг, масштабирование, вращение) и операцию проецирования в одну общую матрицу преобразования. Аналогично можно поступить с приведением спроецированных точек к экранным координатам: Математические основы компьютерной графикиМатематические основы компьютерной графики
    Таким образом, все операции преобразования объекта трехмерного пространства на картинную плоскость (экран) можно описать в терминах матричных умножений. Математические основы компьютерной графики

    Предмет, задачи и применение машинной графики

    Долгое время машинной графикой могли позволить себе пользоваться и заниматься лишь наиболее передовые в техническом отношении организации (институты военной и космической техники, крупные архитектурно-строительные, автомобиле- и авиастроительные фирмы и корпорации). Однако, в последние десятилетия электроника добилась больших успехов в повышении мощности и одновременно снижении стоимости и габаритов вычислительной техники. Миниатюрные персональные компьютеры сейчас имеют мощность и быстродействие значительно большее, чем занимающие целые залы установки 15-20 летней давности. Мышление и программирование на языке графических образов становится неотъемлемой частью процесса обучения, а машинная графика – привычным занятием людей самых разных профессий.
    Машинная графика – это совокупность методов и приемов для преобразования при помощи персонального компьютера данных в графическое представление или графическое представление в данные. Таким образом, машинная графика представляет собой комплекс аппаратных и программных средств для создания, хранения, обработки и наглядного представления графической информации с помощью компьютера.
    Обработка информации, представленной в виде изображений, с помощью персонального компьютера имеет несколько разновидностей и практических приложений. Исторически сложилось так, что область манипулирования с изображениями, разделяют на три направления: компьютерная (машинная) графика, обработка изображений, распознавание (анализ) образов.
    В задачи компьютерной графики входит синтез (воспроизведение) изображения, когда в качестве исходных данных выступает смысловое описание объекта (образа). Простейшие примеры задач компьютерной графики: построение графика функции одной переменной y=f(x) , визуализация процесса вращения трехмерного тела (куб, тетраэдр и т.д.), синтез сложного рельефа с наложением текстуры и добавлением источника света. Здесь также можно выделить бурно развивающуюся в настоящее время интерактивную компьютерную графику. Это система, с которой пользователь может вести "диалог" на уровне команд.
    Примерами могут быть всевозможные системы автоматизированного проектирования (САПР), геоинформационные системы (ГИС), компьютерные игры.

    Обработка изображений представляет собой направление, в задачах которого в качестве входной и выходной информации выступают изображения (матрицы пикселей). Примеры подобных задач: увеличение/уменьшение яркости в изображении, получение изображения в оттенках серого (grayscale), повышение контраста, устранение шумовых элементов, размытие изображения, выделение границ на изображении и др. Причем количество выходных изображений может быть больше одного, например, восстановление трехмерной модели фигуры (тела) по ее проекциям.

    Задачей распознавания образов является применение математических методов и алгоритмов, позволяющих получать некую описательную (смысловую) информацию о заданном изображении. Распознавание (анализ) образов можно представить себе как обратная задача компьютерной графики. Процедура распознавания применяется к некоторому изображению и преобразует его в некоторое абстрактное описание: набор чисел, цепочку символов и т.д. Следующий шаг позволяет отнести исходное изображение к одному из классов.

    Эти три направления можно представить следующей таблицей.

    Основные задачи
    Синтез изображенийАнализ изображенийОбработка изображений
    ВходФормальное описание, графические указания, команды оператора (пользователя)Визуальное представлениеВизуальное представление
    ВыходВизуальное представлениеФормальное описаниеВизуальное представление
    ЦелиГенерация и представление изображенийРаспознавание образов, структурный анализ, анализ сценПовышение качества изображений
    Как научную и учебную дисциплину машинную графику можно считать одним из специальных разделов информатики. Теория машинной графики развивается на базе взаимных связей информатики с другими науками и учебными дисциплинами, такими, как начертательная, проективная, аналитическая и дифференциальная геометрии, топология, черчение, вычислительная математика, операционные системы и языки программирования.


    Высокая точность, быстрота и аккуратность автоматизированного выполнения чертежно- конструкторских работ, возможность многократного воспроизведения изображений и их вариантов, получение динамически изменяющихся изображений машинной мультипликации – вот не полный перечень достоинств машинной графики.

    Машинная графика становится все более доступным и популярным средством общения человека с компьютером. Знание азов компьютерной графики и умение их использовать на простейшем бытовом уровне становится неотъемлемыми элементами грамотности и культуры современного человека.

    Машинная графика широко применяется в системах автоматизированного проектирования (САПР) различных изделий. Конструкторы средствами машинной графики получают чертежи отдельных типовых деталей и сборочные чертежи узлов. Используя различные манипуляторы, инженеры могут многократно изменять виды и конструктивные характеристики проектируемого изделия.

    Архитектор, рассматривая задуманную композицию в различных ракурсах, может многократно изменять ее, сравнивать десятки вариантов, на прорисовку которых вручную у него ушло много времени. Сочетание фототехники с машинной и ручной графикой значительно расширяет область применения компьютерной графики.

    Машинная графика позволяет дизайнеру формировать геометрические объекты и наблюдать на экране дисплея их образы в различных ракурсах на всех этапах творческого процесса. С помощью ее средств автоматически изготавливаются объемные модели, сложные литейные формы и штампы, минуя трудоемкие шаблонные работы. Обувь и одежда могут конструироваться также средствами машинной графики, включенной в систему САПР.

    При исследованиях в различных областях науки и техники компьютерная и машинная графика наглядно представляет результаты расчетных процессов и обработки экспериментальных данных. Компьютер строит модели и мультипликационные кадры, отображающие физические и химические процессы, структуры молекул, конфигурации электромагнитных полей. Средствами машинной графики воспроизводятся переданные из космоса снимки других планет и комет, а также томограммы и другие изображения в медицине и биологии.


    Машинная графика применяется для моделирования (имитации) непредсказуемых ситуаций при подготовке на электронных тренажерах водителей автомобилей, летчиков, пилотов космических кораблей. Компьютерная модель автомобиля, "врезавшегося" в модель стены, позволяет инженеру проанализировать, что произошло с моделями пассажиров, и усовершенствовать конструкцию автомобиля.

    Метрическая точность и высокая скорость изготовления машинных чертежей обуславливает их широкое применение в картографии и топографии.

    Машинная графика экономит труд и время художника-мультипликатора, позволяя ему рисовать только ключевые кадры эпизода, создавая без участия художника (автоматически) все промежуточные картинки.

    Художники и режиссеры создают с помощью компьютеров не только заставки для кино и телепередач, но и компьютерные фильмы, восхищая зрителя фейерверками красок, форм, фантазии, скорости и звуков.

    Машинная графика широко используется в компьютерных играх, развивающих у человека фантазию, изобретательность, логику, скорость реакции и любознательность. Современные компьютерные игры своей популярностью обязаны именно машинной графике.

    Наглядность и доступность графического представления информации, мощные изобразительные возможности обеспечивают машинной графике прочное место и в учебном процессе. Даже школьники начальных классов работают с графическими терминалами как с инструментом для рисования и создания графических композиций, что весьма полезно для развития воображения, живости ума и скорости реакции.

    Многие разделы математики, физики, информатики и других дисциплин могут быть достаточно успешно освоены только с привлечением зрительных образов, графических изображений и иллюстраций. Поэтому главной частью современного арсенала педагогического инструмента таких разделов являются хорошо подобранные иллюстрации на экранах компьютера. В практику преподавания различных дисциплин все более активно вводятся автоматизированные обучающие системы, в которых основная психолого-педагогическая нагрузка возложена именно на средства машинной графики.


    Следует отдельно отметить область, которая сейчас проникла во все сферы человеческого бытия. Речь идет о трехмерной (3D) графике как подразделе компьютерной графике в целом.

    Окружающий нас мир вещей не плоский. Мы живем в мире трехмерных объектов. Компьютеры пытаются вызвать у нас те же ощущения, что возникают от реального мира, помещая его копию на свои экраны. Экран дисплея приоткрывает дверь в огромный трехмерный мир. Третье измерение (глубина) резко увеличивает количество информации, доступной пользователю в данный момент. Придавая графике глубину, мы создаем модель мира, который можно исследовать теми же интуитивно привычными нам методами, какими мы познаем окружающий нас реальный мир.

    В процессе формирования изображений присутствует по крайней мере две сущности: объект и наблюдатель (камера). Объект существует в пространстве независимо от кого-либо. В компьютерной графике имеют дело, как правило, с воображаемыми объектами. Любая система отображения должна обладать средствами формирования изображений наблюдаемых объектов. В качестве такого средства может выступать человек или фотокамера. Именно наблюдатель формирует изображение объектов. Хотя и наблюдатель и наблюдаемый объект существуют в одном и том же трехмерном мире, создаваемое при этом изображение получается двухмерным. Суть процесса формирования изображения и состоит в том, чтобы, зная положение наблюдателя и положение объекта, описать (синтезировать) получаемое при этом двухмерное изображение (проекцию).

    Процесс формирования изображения с помощью персонального компьютера может быть описан следующей блок-схемой.

    Предмет, задачи и применение машинной графики

    Взаимодействие между прикладной программой и графической системой – это множество функций, которые в совокупности образуют графическую библиотеку. Спецификация этих функций и есть то, что обычно называют интерфейсом прикладного программирования (API – application programmer’s interface). Для программиста, занимающегося разработкой прикладной программы, существует только API, и он избавлен от необходимости вникать в подробности работы аппаратуры и программной реализации функций графической библиотеки.


    Существует много различных API: OpenGL, PHIGS, Direct3D, VRML, JAVA3D. В составе любого API должны присутствовать функции, которые позволяли бы описывать следующие сущности трехмерной сцены:

  • Объекты;
  • Наблюдателя (камеру);
  • Источники света;
  • Свойства материалов объекта.


  • Для описания объектов чаще всего используют массивы вершин. Изначально объект представляется в виде набора точек или значений координат в трехмерной координатной сетке. В большинстве API (графических библиотеках) в распоряжение пользователя предоставляется практически один и тот же набор примитивов. Типовой набор включает точки, отрезки прямых, треугольники, многоугольники, а иногда и текст.

    Описать наблюдателя или камеру можно различными способами. Доступные на сегодняшний день графические библиотеки отличаются как гибкостью, которую они обеспечивают при выборе параметров камеры, так и количеством имеющихся в распоряжении пользователя методов ее описания. Как правило, для камеры задают четыре типа параметров, однозначно определяющих характеристики создаваемого ею изображения.

  • Положение камеры задается положением центра проекции;
  • Ориентация. Расположив центр проекции в определенной точке пространства, можно совместить с ним начало локальной системы координат камеры и вращать ее относительно осей этой системы координат, изменяя таким образом ориентацию объекта;
  • Фокусное расстояние объектива камеры фактически определяет размер изображения на плоскости проекции;
  • Размеры (высота и ширина) задней стенки камеры.


  • Источник света характеризуется своим положением, интенсивностью, цветом излучения и его направленностью. Во многих API имеются функции для задания таких параметров, причем в сцене может присутствовать несколько источников света с разными характеристиками.

    С точки зрения компьютерной графики наибольшее значение имеет возможность реализовать конвейерный принцип обработки информации. Этот принцип означает, что необходимо выполнять вычисления по одним и тем же формулам с разными данными. Именно в задачах трехмерной графики присутствует такой случай – нужно многократно обрабатывать по одним и тем же формулам список вершин, характеризующих отображаемые объекты.


    Предположим, что имеется множество вершин, определяющих графические примитивы, из которых формируется изображение. Поскольку все объекты представлены в терминах координат положения точек в пространстве, можно рассматривать множество типов примитивов и вершин как геометрические данные. Сложная сцена может описываться тысячами, если не миллионами, вершин. Все их нужно обработать по одному алгоритму и в результате сформировать в буфере кадра описание растра. Если рассматривать этот процесс в терминах геометрических операций с исходными данными, то можно представить его в виде следующей блок-схемы.

    Предмет, задачи и применение машинной графики

  • Геометрические преобразования

    Большинство этапов обработки графической информации можно описать в форме геометрических преобразований представления объектов сцены в разных системах координат. Очевидно, что основная часть процесса визуализации представляет собой преобразование представления объектов из базовой (мировой) системы координат в систему координат камеры. Внутреннее представление геометрических объектов – будь то в системе координат камеры или в любой другой подходящей системе координат, используемой в графическом API, - должно быть преобразовано на этой стадии в представление в системе координат устройства отображения (дисплей, принтер). Каждое такое преобразование можно представить в матричной форме, причем последовательные преобразования выражаются перемножением (конкатенацией) соответствующих матриц элементарных преобразований. В результате формируется матрица комплексного преобразования.

  • Отсечение

    Вторая важная операция в графическом конвейере – отсечение (clipping) . Необходимость в ней возникает по той простой причине, что имеющиеся в нашем распоряжении средства отображения сами по себе имеют конечные размеры. Отсечение выполняется на разных этапах формирования изображения. Отсечение геометрических примитивов можно выполнить, анализируя только координаты.

  • Проективное преобразование

    Как правило, при обработке геометрической информации трехмерное описание объектов стараются сохранить как можно дольше по мере продвижения по "по конвейеру".


    Но после стадий геометрических преобразований и отсечения неизбежно наступает момент, когда те объекты, которые попадают в поле видимости, нужно преобразовать из трехмерной формы в двухмерную. Существует множество видов проективного преобразования, некоторые из которых позволяют использовать математический аппарат операций с матрицами размером 4x4.

  • Растровое преобразование

    Последний этап процесса – преобразование описания двухмерных объектов в коды засветки пикселей в буфере кадра. Поскольку регенерация изображения выполняется аппаратно, этот процесс практически скрыт от прикладного программиста, и можно считать, что последняя операция геометрического конвейера – это растровое преобразование.



  • Конвейерная архитектура обработки геометрических данных занимает сейчас доминирующее положение среди существующих на сегодняшний день структур аппаратных средств графических систем, в особенности тех систем, которые должны формировать динамические изображения в реальном масштабе времени.

    Библиотека DirectX

    С ростом производительности персональных компьютеров и появлением видеоакселераторов нового поколения трехмерная графика быстро распространилась в большинстве сфер жизни пользователей: дома, в малом и среднем офисе, игровых залах. Одновременно с развитием новых технологий ускорения видеоизображения началось на первый взгляд скрытое, но ожесточенное сражение производителей чипсетов, видеокарт и программного обеспечения по завоеванию любви и признания конечного пользователя. Кроме этой конкурентной борьбы присутствует еще и более глобальная – признание одного из промышленных стандартов большинством разработчиков компьютерных игр и производителями графических видеокарт. Объектом борьбы в данном случае выступает один из прикладных программных интерфейсов (API – Application Programming Interfaces). На данный момент среди общего числа подобных API остались только две графические библиотеки, которые можно рассматривать как основные – DirectX корпорации Microsoft и OpenGL – компании Silicon Graphics.
    DirectX – совокупность технологий, разработанных корпорацией Microsoft с целью превратить Windows в оптимальную платформу для мультимедийных приложений и компьютерных игр с полноцветной графикой, видео, трехмерной анимацией и объемным звуком. История появления технологии DirectX уходит к 1995 году, когда под пристальное внимание корпорации попала британская компания RenderMorphics с небольшим проектом, представленным на обычной выставке. Этот проект умел отображать неплохие трехмерные объекты в реальном времени на обычном персональном компьютере, доступном каждому. После приобретения данной компании, Microsoft приступает к разработке графической библиотеки под Windows 95. Данная разработка вылилась в создание нового API, который дал разработчикам игр более прямой доступ до аппаратного обеспечения, увеличив тем самым производительность игр под Windows. В данном тексте речь будет идти о девятой версии DirectX.
    Вообще, DirectX – набор API функций, предоставляющий низкоуровневый интерфейс к аппаратным средствам (ускорители 3D графики, звуковые и сетевые платы) персонального компьютера. Этот набор функций позволяет не привязываться жестко к тем или иным аппаратным средствам и не требует написания аппаратно-зависимого кода (если аппаратные средства не поддерживают каких-либо возможностей, то они эмулируются на программном уровне).
    DirectX представляет собой набор следующих основных компонент:
  • DirectX Graphics (компонент, который управляет всем графическим выводом; этот API предоставляет функции для работы с 2D и 3D рисованием)
  • DirectInput (компонент, включающий поддержку (API) таких устройств ввода как клавиатура, мышь, джойстик)
  • DirectPlay (компонент, поддерживающий работу с коммуникационной средой (сетью); не зависит от сетевого протокола и метода соединения)
  • DirectSound (компонент, позволяющий микшировать звук в реальном времени и предоставляющий прямой доступ к звуковой карте)
  • DirectShow (компонент для работы с новой периферией – цифровыми фото и видеокамерами)


  • Графическая библиотека Direct3D

    В данном курсе автором будут рассматриваться примеры работы библиотеки Direct3D с использованием языков программирования C++ (среда Microsoft Visual Studio) и Pascal (среда Delphi). Предполагается, что читатель уже установил и настроил все необходимые программные средства. Для успешной работы с графической библиотекой Direct3D в среде Microsoft Visual Studio необходимо установить набор Microsoft DirectX Software Development Kit, который можно скачать с сайта http://www.microsoft.com. Для пользователей, которые планируют разрабатывать подобные проекты на языке Pascal в среде, например, Delphi, требуется наличия в системе заголовочных файлов, найти которые можно по адресу http://www.clootie.ru. Кроме этого в системе должен присутствовать пакет библиотек DirectX End-User Runtimes. На момент написания этих строк в сети Интернет была доступна версия DirectX 9.0c с обновлениями за июнь 2006 года.
    Непосредственно начало работы с библиотекой Direct3D должна начинаться с подключения заголовочных файлов к проектам.
    В среде Microsoft Visual Studio подключение заголовочного файла Direct3D SDK проделывается следующим образом:
    … #include
    В проектах на Delphi строчка подключения должна выглядеть так:
    … uses …, Direct3D9; …
    Кроме этого для проектов Visual Studio требуется еще подключить статическую библиотеку d3d9.lib. Это можно проделать либо указанием в настройках проекта, либо явно прописать в коде посредством директивы препроцессора:
    #pragma comment (lib, "d3d9.lib").
    Следующий шаг заключается в объявлении указателя на главный интерфейс IDirect3D9 и указателя на интерфейс устройства.
    В языке C++ эта строчка кода будет выглядеть следующим образом:
    … LPDIRECT3D9 direct3d = NULL; LPDIRECT3DDEVICE9 device = NULL; …
    Для языка Pascal эти объявления можно записать так:
    … var direct3d: IDirect3D9; device: IDirect3DDevice9; …
    Вся работа начинается с создания главного объекта. Именно создание главного объекта Direct3D позволит осуществить доступ ко всем возможностям, предоставляемым его интерфейсами.
    Создание главного объекта – это вызов предусмотренной функции (Direct3DCreate9) с единственным параметром (D3D_SDK_VERSION). D3D_SDK_VERSION – это предопределенная константа, описанная в заголовочном модуле (d3d9.h или Direct3D9.pas) и указывающая номер версии библиотеки DirectX. В этом можно убедиться, заглянув в справку помощи по DirectX.

    D3D_SDK_VERSION
    Версия DirectXЧисловое значение константы
    8.0120
    8.1220
    9.031
    9.0a31
    9.0b31
    9.0c32
    В результате чего, указав необходимую версию D3D_SDK_VERSION, можно рассчитывать и на определенные возможности, поддерживаемые созданным объектом. Программная конструкция по созданию главного объекта Direct3D будет выглядеть следующим образом:

    C++direct3d = Direct3DCreate9( D3D_SDK_VERSION );
    Pascaldirect3d := Direct3DCreate9( D3D_SDK_VERSION );
    После создания объекта Direct3D функция Direct3DCreate9() возвращает указатель на интерфейс либо пустое значение, если вызов произошел с ошибкой. Это может свидетельствовать о том, что на Вашем компьютере отсутствует библиотека DirectX.

    Через главный объект мы не можем производить вывод графики. Используя его, мы можем только узнать возможности и специфическую информацию о видеокарте и создать устройство, представляющее видеоадаптер. А вот уже с помощью устройства мы можем рисовать, накладывать текстуры, освещать сцену и т.д.

    Прежде чем разбираться с методом создания устройства вывода, необходимо понять, как устроен процесс рендеринга в библиотеке Direct3D и вывод результата на экран. При визуализации графических примитивов непосредственно в окно вывода, пользователь будет наблюдать заметное "мерцание" движущихся объектов, что не совсем пригодно для создания анимационных сцен. Решением этой проблемы является использование метода двойной буферизации вывода, когда создается временный образ (область в памяти), в который производится вывод всех графических операций, а затем он целиком отображается (копируется) в окно вывода. Именно так и работает графическая библиотека Direct3D.


    Весь процесс "отрисовки" или рендеринга происходит в так называемый BackBuffer (задний буфер). Все что Direct3D выводит на экран, рисуется (помещается) в BackBuffer, а затем копируется в первичный буфер (FrontBuffer). В терминологии DirectX буфер – это определенный кусок памяти, в котором лежит некоторая графическая или не совсем информация. В данном случае BackBuffer – это место в памяти видеоадаптера отведенное под данные, которые будут показаны на экран по требованию. При этом следует заметить, что формат переднего и заднего буферов должны совпадать, т.е. у них должны быть одинаковые размеры, количество цветов и т.д.

    Следующим шагом является получение текущих установок рабочего стола, а именно, какой формат пикселя (сколько битов отведено под каждую составляющую цвета) присутствует в данный момент. Для этого можно воспользоваться, вызвав метод GetAdapterDisplayMode главного объекта Direct3D. Примеры вызовов этого метода для языков C++ и Pascal приведены в следующей таблице:

    C++

    D3DDISPLAYMODE display; … direct3d ->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &display );
    Pascal

    var display: TD3DDisplayMode; … direct3d.GetAdapterDisplayMode( D3DADAPTER_DEFAULT, display );
    У метода GetAdapterDisplayMode имеются два аргумента:

  • константа, определяющая номер (индекс) видеоадаптера, для которого запрашиваются параметры;
  • указатель на переменную, в которую помещается результат выполнения команды.


  • Если у вас в системе присутствует всего один видеоадаптер, то в качестве первого параметра можно передавать ноль. Второй аргумент представляет собой переменную структурного типа следующего содержания:

    C++

    typedef struct _D3DDISPLAYMODE { UINT Width; UINT Height; UINT RefreshRate; D3DFORMAT Format; } D3DDISPLAYMODE;
    Pascal

    TD3DDisplayMode = packed record Width: LongWord; Height: LongWord; RefreshRate: LongWord; Format: TD3DFormat; end {_D3DDISPLAYMODE};
    Первые два поля данной структуры определяют соответственно ширину и высоту рабочего стола, третье поле содержит частоту обновления монитора, и последнее поле – формат пикселя.


    Нас интересует только последнее поле этой структуры для того, чтобы использовать текущие установки в механизме двойной буферизации.

    Следующий шаг – заполнение структуры D3DPRESENT_PARAMETERS, которая будет задавать параметры поверхности вывода (рендеринга). Для этого необходимо объявить вспомогательную переменную и заполнить ее поля, например, следующим образом:

    C++

    D3DPRESENT_PARAMETERS params; … ZeroMemory( ¶ms, sizeof(params) ); params.Windowed = TRUE; params.SwapEffect = D3DSWAPEFFECT_DISCARD; params.BackBufferFormat = display.Format; …
    Pascal

    var params: TD3DPresentParameters; … ZeroMemory( @params, SizeOf(params) ); params.Windowed := True; params.SwapEffect := D3DSWAPEFFECT_DISCARD; params.BackBufferFormat := display.Format; …
    Здесь приведены строки для минимального набора обязательных действий. Строка ZeroMemory(…) заполняет указанную в качестве первого параметра структуру нулями. Строка "params.Windowed := …" указывает, что вывод будет производиться в некоторое окно. Строка "params.SwapEffect := …" задает режим работы механизма двойной буферизации. И последняя строка "params.BackBufferFormat := …" указывает какой формат буфера будет использоваться.

    Следующий шаг инициализационных действий состоит в создании устройства вывода. Это действие реализуется с помощью вызова метода CreateDevice главного объекта:

    C++direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_ SOFTWARE_VERTEXPROCESSING, ¶ms, &device )

    Pascaldirect3d.CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, @params, device );
    В результате вызова метода CreateDevice получаем ссылку на интерфейс IDirect3DDevice9, с помощью которого мы будем производить рендеринг сцены. Поясним значения параметров метода CreateDevice. Первый параметр (D3DADAPTER_DEFAULT) указывает номер адаптера, установленного в системе. Второй аргумент метода (D3DDEVTYPE_HAL) определяет тип устройства; значение D3DDEVTYPE_HAL - позволяет использовать аппаратное ускорение на 100%, D3DDEVTYPE_REF – указывает использовать только программные действия (ресурсы центрального процессора).


    Третий параметр позволяет задать окно (Handle), куда будет производиться вывод сцены. Четвертый аргумент (D3DCREATE_SOFTWARE_VERTEXPROCESSING) указывает, что обработка вершин сцены будет производиться по фиксированным заданным правилам (этот параметр следует указывать, если видеоадаптер не поддерживает архитектуру шейдеров). Предпоследний – пятый параметр хранит параметры создаваемого устройства вывода. И последний аргумент – это имя переменной, в которую при успешном вызове будет помещен результат работы метода.

    Таким образом, схему работы библиотеки Direct3D можно представить так.

    Графическая библиотека Direct3D

    После того, как все наши инициализации и настройки устройства вывода проведены, наступает заключительный шаг, которой состоит в непосредственном построении и отображении сцены на экране дисплея. Наверно самым простым примером построения сцены является вывод пустого окна, закрашенного определенным цветом. Это можно реализовать с помощью метода Clear, который содержится в интерфейсе IDirect3DDevice9. Этот метод закрашивает задний буфер (BackBuffer) указанным цветом. Программная реализация такого вывода будет следующая:

    C++device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0);
    Pascaldevice.Clear(0,nil,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0, 0);
    Первый параметр метода Clear обозначает количество прямоугольников для очистки области вывода (0 обозначает, что очистке подвергается вся область вывода). Второй параметр представляет собой указатель на массив, содержащий набор величин типа TRect. Каждая отдельная структура указывает, какое место в поверхности вывода следует очищать (значение NULL требует очистки всей поверхности отображения). Третий параметр обозначает что именно (какую поверхность) следует очищать. В данном случае очистке подвергается только поверхность вывода. Кроме этого, метод Clear может использоваться для очистки Z-буфера (константа D3DCLEAR_ZBUFFER) и буфера трафарета (константа D3DCLEAR_STENCIL). Четвертый параметр указывает, каким цветом следует заполнять поверхность рендеринга.


    Функция D3DCOLOR_XRGB(Red,Green,Blue) возвращает цвет из трех составляющих – красного, зеленого и синего цветов. Значения параметров должны лежать в диапазоне [0,…,255]. Предпоследний параметр указывает значение, которым будет заполнен буфер глубины (Z-буфер). Значения этого параметра должны лежать в диапазоне [0.0,…,1.0], где 0 – соответствует ближайшей границе, 1 – дальней. Последний параметр метода задает значение для заполнения буфера шаблона (Stencil буфера).

    Следующий шаг процесса рендеринга – это непосредственный вывод содержимого заднего буфера (BackBuffer) в окно визуализации. Этот шаг еще называют переключением буферов, и осуществляется он с помощью метода Present интерфейса IDirect3DDevice9. Программный код с использованием этого метода выглядит следующим образом:

    C++device->Present( NULL, NULL, NULL, NULL );
    Pascaldevice.Present(nil, nil, 0, nil);
    Интерес здесь представляет, как правило, только третий параметр. Если он равен нулю, то идентификатор окна, в который происходит вывод, берется из ранее установленного, при создании устройства. Все остальные параметры выставляют, как правило, в NULL. Таким образом, минимальный набор инструкций для процедуры рендеринга, состоит в вызове двух методов Clear и Present интерфейса IDirect3DDevice9.

    Подытожив все рассмотренные шаги по инициализации и процедуре рендеринга, можно все их представить в виде следующей блок-схемы.

    Графическая библиотека Direct3D

    Таким образом, последовательность шагов инициализации библиотеки Direct3D может выглядеть следующим образом:

    C++

    direct3d = Direct3DCreate9( D3D_SDK_VERSION ); direct3d ->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &display );

    ZeroMemory( ¶ms, sizeof(params) ); params.Windowed = TRUE; params.SwapEffect = D3DSWAPEFFECT_DISCARD; params.BackBufferFormat = display.Format;

    direct3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, ¶ms, &device );
    Pascal

    direct3d := Direct3DCreate9( D3D_SDK_VERSION ); direct3d.GetAdapterDisplayMode( D3DADAPTER_DEFAULT, display );

    ZeroMemory( @params, SizeOf(params) ); params.Windowed := True; params.SwapEffect := D3DSWAPEFFECT_DISCARD; params.BackBufferFormat := display.Format;

    direct3d.CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Handle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, @params, device );

    Технология COM

    DirectX API базируется на технологии COM (Component Object Model) – компонентная модель объектов. Основная идея технологии COM заключается в работе с указателями на виртуальную таблицу функций. COM объект это обычный DLL файл, который зарегистрирован в системе. Доступ к COM объектам осуществляется через так называемые интерфейсы. Один COM объект может содержать в себе несколько интерфейсов. Один интерфейс представляет собой набор функций, объединенных общим назначением. Любой COM объект содержит в себе (наследуется от) интерфейс IUnknown.
    Интерфейс IUnknown обеспечивает два базовых свойства COM-объектов:
  • Подсчет количества обращений к объекту
  • Способность запрашивать другие интерфейсы

  • При помощи интерфейса lUnknown можно определить, какие еще интересующие вас интерфейсы поддерживаются объектом. Схематично подход работы технологии COM можно представить следующей блок-схемой.
    Технология COM

    Основной принцип работы с технологией COM выражается следующими постулатами:
  • Имеется указатель на таблицу интерфейсов;
  • Каждая ячейка содержит указатели на методы того или иного интерфейса в отдельности;
  • Вызов нужного метода напрямую невозможен.

  • Для того чтобы вызвать необходимый метод, программист должен поделать следующие шаги:
  • Создать объект;
  • Получить указатель на соответствующий интерфейс;
  • Используя данный указатель вызвать нужный метод.

  • На языке C++ данный механизм вызова метода может быть реализован так:
    // создали объект и получили указатель на интерфейс IDirect3D9 pd3d = Direct3DCreate9 (…);
    // вызвали метод CreateDevice полученного интерфейса (IDirect3D9) pd3d -> CreateDevice (…);
    Механизм интерфейсов имеет обратную совместимость, т.е. приложения, которые используют объекты, например, DirectX7, будут работать и с DirectX9. Следует особо отметить, что разработка приложений для DirectX может вестись с использованием языков высокого уровня: Visual Basic, C++, C#, Pascal (Delphi).
    Процесс взаимодействия приложения с видеокартой может быть описан следующей блок-схемой:
    Технология COM

    Архитектура DirectX в своей работе предусматривает так называемый уровень абстрагирования аппаратных средств – HAL (Hardware Abstraction Layer), который функционирует как промежуточное звено между программным обеспечением и аппаратурой, позволяя разработчикам обращаться к тем или иным компонентам, не зная их марки, модели и других деталей. В результате чего такой уровень абстракции оборудования позволяет настроить работу при любом аппаратном обеспечении. Особо следует отметить, что драйверы устройств (видео-, звуковых и сетевых карт) обычно создаются (пишутся) самими производителями оборудования.

    Вывод простейших примитивов

    Построение любой сцены в Direct3D, будь это обычная плоская кривая, например, график функции одной переменной, или сложная трехмерная композиция, происходит с помощью простейших геометрических примитивов, таких как точка, отрезок прямой и треугольник. Элементарным строительным материалом для перечисленных примитивов является вершина. Именно набором вершин задается тот или иной примитив. Чтобы использовать вершины при построении сцены необходимо определить их тип. Для хранения вершин отведем область памяти, представляющую собой обычный массив определенного типа. Вначале необходимо описать, что из себя будет представлять вершина – задать определенный формат. Программно этот шаг реализуется через структуры следующим образом:
    C++
    struct MYVERTEX1 { FLOAT x, y, z, rhw; DWORD color; }; MYVERTEX1 data1[100];
    struct MYVERTEX2 { FLOAT x, y, z; FLOAT n1, n2, n3; }; MYVERTEX2 data2[100];
    Pascal
    MyVertex1 = packed record x, y, z, rhw: Single; color: DWORD; end;
    MyVertex2 = packed record x, y, z: Single; n1, n2, n3: Single; end;
    var data1: array [0..99] of MyVertex1; data2: array [0..99] of MyVertex2;

    Тем самым данные о вершинах будут храниться в массиве, и иметь строго определенный формат (тип). В процессе визуализации сцены необходимо указать графической библиотеке, что собой представляет одна вершина примитива. Это реализуется с помощью механизма флагов, называемого FVF (Flexible Vertex Format – гибкий формат вершин). Существует порядка двух десятков флагов FVF для определения формата вершины. Самые распространенные и наиболее используемые флаги FVF для определения формата вершины представлены в следующей таблице:
    D3DFVF_DIFFUSEиспользуется цветовая компонента вершины
    D3DFVF_NORMALвершина содержит нормали
    D3DFVF_TEX1задает текстурные координаты вершины
    D3DFVF_PSIZEопределяет размер частицы
    D3DFVF_XYZвершина содержит три координаты
    D3DFVF_XYZRHWпреобразованный формат вершин

    Комбинация конкретных флагов дает возможность сообщить системе, с каким форматом (типом) вершин она имеет дело в данный момент времени.
    Так, например,
    (D3DFVF_XYZ OR D3DFVF_NORMAL) – вершина содержит нормали и координаты, (D3DFVF_XYZRHW OR D3DFVF_DIFFUSE) - цветная преобразованная вершина, где OR – операция логического "или". Установка формата вершин осуществляется с помощью вызова метода SetFVF интерфейса IDirect3DDevice9. Программно это реализуется так:
    C++
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) … device->SetFVF(MY_FVF);
    Pascal
    const MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE; … device.SetFVF(MY_FVF);

    Следует особо остановиться на флаге D3DFVF_XYZRHW. Этот флаг позволяет описывать вершины, определенные на плоскости. Он служит для графической системы индикатором того, что построение сцены происходит на плоскости в координатах окна (формы) вывода. При этом формат вершины должен задаваться четверкой чисел x,y,z,w, две последних из которых, как правило, не используются, однако должны присутствовать при описании вершины.
    В качестве примера попытаемся вывести на экран 100 точек, случайно "разбросанных" на форме. Для этого необходимо проделать следующие шаги:
  • Описать структуру, в которой будут храниться координаты точек;
  • Объявить массив нужной размерности (в нашем случае на 100 элементов) и заполнить его данными (в нашем случае случайными координатами).
  • Вызвать метод для вывода этого массива на экран.

  • Первые два шага нам реализовать уже не составляет особого труда. Третий же шаг может быть реализован помощью вызова метода DrawPrimitiveUP интерфейса IDirect3DDevice9. Прототип данного метода выглядит так:
    DrawPrimitiveUP( Тип выводимого примитива, Количество примитивов, Массив данных, Размер "шага в байтах" от одной вершины до другой )
    Первый параметр указывает на тип выводимого примитива и задается одной из следующих констант: D3DPT_POINTLIST, D3DPT_LINELIST, D3DPT_LINESTRIP, D3DPT_TRIANGLELIST, D3DPT_TRIANGLESTRIP, D3DPT_TRIANGLEFAN. Второй аргумент задает количество выводимых примитивов. Третий параметр является указателем на область в памяти, где располагаются данные о вершинах (в нашем случае это заполненный массив).


    И последний параметр задает, сколько байтов отводится для хранения одной вершины.
    Возвращаясь к нашему примеру, вывод ста случайных точек на плоскости можно осуществить, например, с помощью следующего программного кода:
    C++
    struct MYVERTEX { FLOAT x, y, z, rhw; }data[100];
    #define MY_FVF (D3DFVF_XYZRHW);
    // заполнение массива data "случайными" точками …
    // процедура вывода сцены device->SetFVF(MY_FVF); device->DrawPrimitiveUP( D3DPT_POINTLIST, 100, data, sizeof(MYVERTEX) );
    Pascal
    type MyVertex = packed record x, y, z, rhw: Single; end;
    const MY_FVF = D3DFVF_XYZRHW;
    var data: array [0..99] of MyVertex;
    // заполнение массива data "случайными" точками …
    // процедура вывода сцены device.SetFVF(MY_FVF); device.DrawPrimitiveUP( D3DPT_POINTLIST, 100, data, SizeOf(MyVertex) );

    Остановимся теперь на процедуре непосредственного вывода результатов на экран. Процесс непосредственного воспроизведения примитивов рекомендуют обрамлять двумя действиями. Перед отображением необходимо вызвать метод BeginScene, а после воспроизведения – метод EndScene интерфейса IDirect3DDevice9. Первый метод информирует устройство вывода (видеокарту), что следует подготовиться к воспроизведению результатов. Второй метод сообщает устройству о том, что процесс воспроизведения для текущего кадра закончен и теперь можно осуществлять переключение буферов рендеринга. Таким образом, процедура воспроизведения сцены должна выглядеть приблизительно следующим образом:
    C++VOID Render() { device->BeginScene(); device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0); device->SetFVF(…); device->DrawPrimitiveUP(…); device->EndScene(); device->Present( NULL, NULL, NULL, NULL ); }
    Pascalprocedure Render; begin device.BeginScene; device.Clear(0,nil,D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0,0); device.SetFVF(…); device.DrawPrimitiveUP(…); device.EndScene; device.Present(nil, nil, 0, nil); end;

    Рассмотрим оставшиеся типы примитивов, которые имеются в наличии у библиотеки Direct3D.


  • Вывод отрезков
  • Для построения независимых отрезков первым аргументом метода DrawPrimitiveUP необходимо указать константу D3DPT_LINELIST. При этом следует заметить, что количество выводимых примитивов (второй параметр метода) будет в два раза меньше количества точек в массиве.
  • Для построения связных отрезков первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_LINESTRIP. При этом количество выводимых примитивов будет на единицу меньше количества точек в исходном массиве ладных.

  • Вывод треугольников
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLELIST, то каждая триада (тройка) вершин задает три вершины одного (независимого) треугольника. Количества выводимых примитивов (треугольников) будет в три раза меньше чем размер массива данных.
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLESTRIP, то речь идет о группе связных треугольников. Первые три вершины задают первый треугольник, вторая, третья и четвертая определяют второй треугольник, третья, четвертая и пятая – третий и т.д. В результате получается лента соприкасающихся треугольников. Следует заметить, что использование связных треугольников самый экономный и эффективный способ построений.
  • Если первым аргументом метода DrawPrimitiveUP указывается константа D3DPT_TRIANGLEFAN, то строится группа связных треугольников, но данные здесь трактуются немного иначе. Треугольники связаны подобно раскрою зонтика или веера. По такой схеме удобно строить конусы и пирамиды в пространстве, выпуклые многоугольники и эллипсы в плоскости.


  • Все рассмотренные шесть видов примитивов по способу их построения можно представить в виде следующего рисунка:
    Вывод простейших примитивов

    В предыдущих примерах фигурировали бинарные примитивы (белые фигуры на черном фоне). Чтобы окрасить примитивы нужно задать цвет каждой вершины примитива. Это проделывается в два этапа. Сначала мы должны изменить тип вершины, а затем добавить в комбинацию флагов FVF значение D3DFVF_DIFFUSE.
    C++struct MYVERTEX { FLOAT x, y, z, rhw; DWORD color; }
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
    Pascaltype MyVertex = packed record x, y, z, rhw: Single; color: DWORD; end;
    const MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE;
    <


    Далее каждой вершине необходимо присвоить некоторый цвет. Это можно проделать с помощью макроса-функции D3DCOLOR_XRGB(r, g, b), где в качестве параметров передается тройка основных цветов (веса) – красного, зеленого и синего. При этом, код, отвечающий за визуализацию сцены, не претерпит никаких изменений.
    Очень часто в компьютерной графике возникает задача вывода объекта, состоящего из треугольников, причем каждый треугольник имеет общие вершины с другими треугольниками. Простейшим примером такой задачи можно назвать построение триангуляции Делоне множества точек на плоскости, представленной на следующем рисунке.
    Вывод простейших примитивов

    С одной стороны данная триангуляция имеет 10 треугольников, каждый из которых содержит 3 вершины. Таким образом, для хранения 30 вершин при расходах 20 байт на каждую вершину, необходимо 600 байт машинной памяти. С другой стороны, можно хранить отдельно сами вершины и список номеров (индексов) вершин для каждого треугольника. В этом случае расходы на машинную память будут следующими: 9 вершин по 20 байт на каждую потребует 180 байт, 10 триплетов индексов вершин, например, по 2 байта на индекс потребуют 60 байт. Итого для такой организации хранения данных модели – в нашем случае триангуляции, необходимо 240 байт, что в два с половиной раза меньше чем при организации хранения вершин всех десяти треугольников. Использование дополнительного массива, который будет хранить только лишь номера вершин модели, позволит избежать повторения данных при задании вершин. Так, например, представленная триангуляция может быть описана следующим образом.

    Вывод простейших примитивов

    10 треугольников = {
    1: (0,3,2),
    2: (0,1,3),
    3: (2,3,4),
    4: (3,6,4),
    5: (1,5,3),
    6: (3,5,6),
    7: (1,7,5),
    8: (5,7,6),
    9: (6,7,8),
    10: (4,6,8)
    } = 2 байта/индекс*
    3 индекса*
    10 треугольников =
    60 байт

    Программно этот подход может быть реализован с помощью вызова метода DrawIndexedPrimitiveUP интерфейса IDirect3DDevice9, который имеет 8 параметров. Первый параметр задает тип выводимых примитивов и представляет собой одну константу из уже известного набора.


    Второй параметр определяет минимальный вершинный индекс и, как правило, он устанавливается в значение ноль. Третий параметр определяет количество вершин в модели. Четвертый параметр задает количество выводимых примитивов. Пятый аргумент метода представляет собой указатель на массив, содержащий набор индексов. Шестой аргумент определяет формат индексных данных и может принимать два значения: D3DFMT_INDEX16 и D3DFMT_INDEX32. Т.е. под один индекс может быть отведено либо 16, либо 32 бита машинной памяти. Предпоследний, седьмой аргумент метода представляет собой указатель на массив, содержащий набор вершин модели. И последний параметр определяет количество байтов, необходимых для хранения одной вершины. Таким образом, обобщив эти строки можно записать формат вызова данного метода:
    DrawIndexedPrimitiveUP( 1. тип выводимых примитивов, 2. минимальный индекс вершин, 3. количество вершин, 4. количество выводимых примитивов, 5. указатель на массив индексов, 6. формат индексов, 7. указатель на массив вершин, 8. количество байтов для хранения одной вершины )
    Предположим, что для каждой вершины представленной выше триангуляции, задан свой цвет, тогда ее визуализация с использованием массива индексов может выглядеть следующим образом:
    C++
    struct MYVERTEX { FLOAT x, y, z, rhw; DWORD color; }
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
    WORD indices[] = {0,3,2, 0,1,3, 2,3,4, 3,6,4, 1,5,3, 3,5,6, 1,7,5, 5,7,6, 6,7,8, 4,6,8};
    MYVERTEX points[] = { { 119, 354, 0, 0, D3DCOLOR_XRGB(255,0,0) }, { 47, 248, 0, 0, D3DCOLOR_XRGB(0,255,0) }, … } … device-> DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 9, 10, indices, D3DFMT_INDEX16, points, sizeof(MYVERTEX) );
    Pascal
    type MyVertex = packed record x, y, z, rhw: Single; color: DWORD; end;
    const MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE; indices: array [0..29] of Word = (0,3,2, 0,1,3, 2,3,4, 3,6,4, 1,5,3, 3,5,6, 1,7,5, 5,7,6, 6,7,8, 4,6,8);
    var points : array [0..8] of MyVertex; … points[0].x := 119; points[0].y := 354; points[0].color := D3DCOLOR_XRGB(255,0,0); …
    device.DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, 9, 10, indices, D3DFMT_INDEX16, points, SizeOf(MyVertex));
    <


    Результат построения триангуляции " с цветом" показан на следующем рисунке.
    Вывод простейших примитивов

    Вообще говоря, функции вывода примитивов DrawPrimitiveUP и DrawIndexedPrimitiveUP являются довольно медленными с эффективной точки зрения, т.к. производят довольно много лишних операций. Для хранения вершин мы использовали обычные переменные (в нашем случае массивы), хотя для таких ситуаций предназначен специальный буфер вершин. Чтобы начать работы с этим буфером вершин необходимо проделать следующие шаги:
  • Объявить переменную, в которой будет храниться адрес буфера вершин. Программно это будет выглядеть так.
    C++LPDIRECT3DVERTEXBUFFER9 VBuffer = NULL;
    Pascalvar VBuffer: IDirect3DVertexBuffer9;

  • Воспользовавшись методом CreateVertexBuffer интерфейса IDirect3DDevice9 создать буфер вершин.
  • Заполнить буфер данными о вершинах. Этот шаг реализуется в три приема. Вначале необходимо запереть буфер, т.к. заполнение его может производиться только в закрытом состоянии. Достигается это вызовом метода Lock интерфейса IDirect3DVertexBuffer9. Второй шаг состоит в непосредственном копировании данных с помощью стандартной функции Win32API – memcpy(). И третий шаг заключается в отпирании буфера вершин с помощью метода Unlock интерфейса IDirect3DVertexBuffer9. Таким образом, этот шаг можно реализовать следующим образом:
    C++
    LPDIRECT3DVERTEXBUFFER9 VBuffer = NULL; VOID* pBuff; … device->CreateVertexBuffer(sizeof(points), 0, MY_FVF, D3DPOOL_DEFAULT, &VBuffer, NULL ); VBuffer->Lock( 0, sizeof(points), (void**)&pBuff, 0 ); memcpy( pBuff, points, sizeof(points) ); VBuffer-> Unlock();
    Pascal
    var VBuffer: IDirect3DVertexBuffer9; pBuff: Pointer; … device.CreateVertexBuffer(SizeOf(points), 0, MY_FVF, D3DPOOL_DEFAULT, VBuffer, nil); VBuffer.Lock(0, SizeOf(points), pBuff, 0); Move( points, pBuff^, SizeOf(points) ); VBuffer.Unlock;

    Разберем значение параметров метода CreateVertexBuffer(). Первый параметр задает размер буфера вершин в байтах. Второй аргумент определяет параметры работы с буфером и, как правило, всегда это значение выставляется в ноль.


    Третий параметр задает формат вершин буфера через набор FVF флагов. Четвертый параметр определяет месторасположение буфера вершин. Значение D3DPOOL_DEFAULT говорит о том, что библиотека сама позаботится о размещении буфера в памяти. Пятый аргумент задает адрес переменной, в которую будет помещен результат вызова метода, т.е. эта переменная будет хранить адрес буфера вершин. И Шестой параметр не используется, является зарезервированным в настоящее время и всегда должен быть пустым.
    Рассмотрим теперь значения параметров метода Lock. Первый параметр определяет смещение от начала буфера, с которого будет производиться запирание области (значение 0 указывает на то, что запирается весь буфер с самого начала). Второй аргумент задает размер запираемой области в байтах. Третий параметр возвращает адрес запираемой области. И последний аргумент задает набор флагов способа запирания и, как правило, всегда равен нулю.
  • И последний шаг – вывод примитивов на экран. Библиотека Direct3D позволяет выводить данные в несколько потоков. Созданный буфер вершин является примером одного такого потока. Вначале вывода сцены на экран необходимо связать наш буфер вершин с одним из потоков данных. Это реализуется с помощью вызова метода SetStreamSource интерфейса IDirect3DDevice9. Этот метод имеет четыре параметра. Первый из них определяет номер потока вывода. Если в программе используется только один буфер вершин, то этот параметр должен быть 0. Второй параметр содержит указатель на переменную, ассоциированную с буфером вершин. Третий – определяет смещение от начала буфера, с которого нужно производить считывание данных. Четвертый параметр задает размер одной вершины в байтах. Далее необходимо указать формат выводимых вершин. Это проделывается с помощью вызова метода SetFVF интерфейса IDirect3DDevice9. И затем производится непосредственный вывод примитивов с помощью функции DrawPrimitive интерфейса IDirect3DDevice9. Метод DrawPrimitive имеет три параметра: первый – тип выводимых примитивов, второй – индекс начальной выводимой вершины и третий определяет количество выводимых примитивов.


    Программно шаг вывода одного треугольника выглядит так:
    C++
    device->SetStreamSource( 0, VBuffer, 0, sizeof(MYVERTEX) ); device->SetFVF( MY_FVF ); device->DrawPrimitive( D3DPT_TRIANGLELIST , 0, 1 );
    Pascal
    device.SetStreamSource(0, VBuffer, 0, SizeOf(MyVertex)); device.SetFVF(MY_FVF); device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);


  • Библиотека Direct3D имеет богатый набор по способу отображения выводимых примитивов. Программист может указать нужный ему способ отображения полигонов с помощью задания режима воспроизведения. Существует три режима воспроизведения полигонов:
  • режим вершинной модели определяет вывод только вершин полигонов без ребер и без закраски внутренних точек примитива и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_POINT) интерфейса IDirect3DDevice9;
  • режим каркасной модели задает вывод только ребер полигонов без закраски внутренних точек примитива и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME) интерфейса IDirect3DDevice9;
  • режим сплошной модели определяет вывод полигонов с закрашенными внутренними точками и реализуется с помощью вызова метода SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID) интерфейса IDirect3DDevice9.

  • Кроме того, сплошная модель вывода подразделяется на два вида закрашивания: плоское заполнение либо закрашивание с интерполяцией. Первое реализуется через вызов метода SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT), второе через SetRenderState(D3DRS_SHADEMODE, D3DSHADE_ GOURAUD).
    Данные режимы воспроизведения можно представить с помощью следующей схемы.
    Вывод простейших примитивов Вывод простейших примитивов

    Мультитекстурирование

    Библиотека Direct3D позволяет накладывать на один полигон не одну, а сразу несколько текстур. Наложение на грань нескольких текстур называется мультитекстурированием. На текущий момент поддерживается до 8 наложений (уровней) текстур на одну грань. Схему (принцип) мультитекстурирования можно описать следующим образом. Первый текстурный уровень (с индексом 0) принимает на вход два значения: цвет текселя и диффузный цвет вершины; производит с ними указанные операции и передает результат на следующий (нижний) уровень. Полученное на предыдущем уровне значение цвета используется в качестве одного из аргумента текущего уровня и т.д. Схематично данные шаги мультитекстурирования можно представить так:
    Мультитекстурирование

    Как уже стало ясно, при мультитекстурировании дело имеют уже с несколькими текстурами. При мультитекстурировании можно для каждого текстурного уровня назначить одни и те же текстурные координаты. В этом случае формат вершины и набор флагов FVF останутся неизмененными. Существует возможность указать, с какими текстурными координатами будет работать тот или иной текстурный уровень. Для этого можно воспользоваться следующими программными строками:
    SetTextureStageState(<уровень>, D3DTSS_TEXCOORDINDEX, <номер координат>);
    Так, например, чтобы указать, что второй уровень будет использовать текстурные координаты первого текстурного уровня, достаточно воспользоваться следующим вызовом метода: SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, 0 ). И затем нужно загрузить (установить) текстуры в соответствующие текстурные уровни.
    SetTexture( 0, tex1 ); SetTexture( 1, tex2 );
    Можно указать для каждого текстурного уровня свои собственные текстурные координаты. В этом случае должен корректно измениться формат вершины при описании и набор FVF флагов. Ниже приведен пример описания вершины и FVF флагов для мультитекстурирования с двумя уровнями (текстурами) и текстурными координатами.
    C++struct CUSTOMVERTEX { FLOAT x, y, z, rhw; // координаты вершины FLOAT u1, v1; // текстурные координаты первого уровня FLOAT u2, v2; // текстурные координаты второго уровня };
    #define MY_FVF (D3DFVF_XYZ | D3DFVF_TEX2)
    Pascaltype MyVertex = packed record x, y, z, rhw: Single; // координаты вершины u1,v1: Single; // текстурные координаты первого уровня u2,v2: Single; // текстурные координаты второго уровня end;
    const MY_FVF = D3DFVF_XYZRHW or D3DFVF_TEX2;
    <
    Вообще, число во флаге D3DFVF_TEX как раз и показывает, сколько текстурных уровней планируется задействовать при мультитекстурировании (D3DFVF_TEX1 … D3DFVF_TEX8). Для хранения второй и последующих текстур необходимо объявить соответствующее количество нужных переменных, произвести загрузку текстур с помощью функции D3DXCreateTextureFromFile() и установить текстуры в соответствующие текстурные уровни вызовов метода SetTexture (<номер уровня>, <текстура>). Ниже приведен пример возможного использования двух текстур при мультитекстурировании.

    C++LPDIRECT3DTEXTURE9 tex, tex2; … D3DXCreateTextureFromFile( device, "texture.bmp", &tex ); D3DXCreateTextureFromFile( device, "texture2.bmp", &tex2 ); … device->SetTexture ( 0, tex ); device->SetTexture ( 1, tex2 );
    Pascalvar tex, tex2: IDirect3DTexture9; … D3DXCreateTextureFromFile( device, 'texture.bmp', tex ); D3DXCreateTextureFromFile( device, 'texture2.bmp', tex2 ); … device.SetTexture( 0, tex ); device.SetTexture( 1, tex2 );

    При такой каскадной схеме мультитекстурирования мы должны для каждого текстурного уровня задать два цветовых аргумента и операцию над ними с помощью вызова метода SetTextureStageState интерфейса IDirect3DDevice9:

    SetTextureStageState( <номер уровня>, D3DTSS_COLORARG1, <значение> ), SetTextureStageState( <номер уровня>, D3DTSS_COLORARG2, <значение> ), SetTextureStageState( <номер уровня>, D3DTSS_COLOROP, <операция> ).

    Помимо уже известных значений констант для цветовых аргументов в данном вызове может быть использована константа D3DTA_CURRENT, которая говорит, что в качестве значения будет браться цвет из предыдущего текстурного уровня.

    Также мы можем указать требуемый тип фильтрации текстур для каждого в отдельности текстурного уровня, указав в качестве первого аргумента его номер и вызвав метод SetSamplerState интерфейса IDirect3DDevice9.

    Вообще говоря, можно использовать даже одну текстуру для реализации механизма мультитекстурирования.


    Для этого необходимо в первый и во второй текстурный уровень загрузить одну и ту же текстуру, но при этом значения текстурных координат для них должны отличатся. Программный код для этого случая может выглядеть следующим образом:

    … points[0].u1 = 0.0f; points[0].v1 = 1.0f; points[0].u2 = 0.0f; points[0].v2 = 3.0f; … device->SetTexture( 0, tex ); device->SetTexture( 1, tex ); …

    Ниже приведен пример мультитекстурирования с помощью одной текстуры и двух текстурных уровней.



    device->SetTexture(0, tex); device->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0); device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); device->SetTexture(1, tex); device->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 1); device->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE); device->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); device->SetTextureStageState(1, D3DTSS_COLOROP, );


    Мультитекстурирование

    operation = D3DTOP_MODULATE4X


    Мультитекстурирование

    operation = D3DTOP_ADD
    Мультитекстурирование

    Текстурирование

    До сих пор выводимые на экран примитивы строились только с помощью синтетических цветов. Для того чтобы придать им большей реалистичности можно прибегнуть к помощи текстур. Текстура представляет собой двумерное растровое изображение, которое накладывается (натягивается) на поверхность объекта, например на плоский треугольник. Текстуры, как правило, хранятся в графических файлах форматов bmp, jpeg, tiff, tga, gif. Библиотека Direct3D содержит богатый набор функций для работы с текстурами. Процесс наложения текстуры на объект называют текстурированием. Текстуры накладываются на объект с помощью так называемых текстурных координат. Текстурные координаты представляют собой пару чисел (u,v), изменяющихся в пределах от 0 до 1, и определяются в своей системе координат. Ось u направлена горизонтально вправо, ось v - вертикально вниз. Пара величин (u,v) однозначно указывает на элемент текстуры, называемый текселем.
    Текстурирование

    Для каждой вершины треугольника мы должны определить текстурные координаты, тем самым, привязав его к некоторой треугольной области на текстуре.
    Текстурирование

    Как уже стало ясным, текстурные координаты изменяют формат вершины. Поэтому данный факт может быть отражен в программе следующим образом.
    C++ struct MYVERTEX { FLOAT x, y, z, rhw; FLOAT u, v; // текстурные координаты };
    #define MY_FVF (D3DFVF_XYZRHW|D3DFVF_TEX1)
    Pascaltype MyVertex = packed record x, y, z, rhw: Single; u,v: Single; // текстурные координаты end;
    const MY_FVF = D3DFVF_XYZRHW or D3DFVF_TEX1;

    Теперь при заполнении данных о вершинах необходимо также указывать и текстурные координаты для каждой вершины текстурируемого примитива. Работа с текстурами в Direct3D осуществляется с помощью интерфейса IDirect3DTexture9. Для этого нужно объявить соответствующую переменную.
    C++LPDIRECT3DTEXTURE9 tex = NULL;
    Pascal
    var tex: IDirect3DTexture9;

    Следующий шаг заключается в загрузке текстуры из графического файла. Чтобы воспользоваться функцией загрузки текстуры необходимо вначале подключить нужные модули и библиотеки.

    C++#include
    Pascaluses …, D3DX9,…
    Загрузка же текстуры из графического файла в память осуществляется с помощью функции D3DXCreateTextureFromFile, у которой имеется три параметра. Первый параметр определяет указатель на устройство вывода, второй аргумент представляет собой строку, содержащую путь до загружаемого файла на диске, и третий – определяет переменную, в которой будет храниться указатель на текстуру.

    C++D3DXCreateTextureFromFile( device, "texture.bmp", &tex )
    PascalD3DXCreateTextureFromFile( device, 'texture.bmp', tex );
    Последний шаг в работе с текстурами заключается в активации нужной текстуры перед выводом примитивов. Реализуется это с помощью вызова метода SetTexture интерфейса IDirect3DDevice9. Данный метод имеет два параметра: первый определяет номер так называемого текстурного уровня, а второй – указатель на загруженную текстуру. Библиотека Direct3D поддерживает до восьми текстурных уровней, но если вы работаете только с одной текстурой, то номер текстурного уровня всегда должен быть нулем. Установка (загрузка текстуры в текстурный уровень) текущей текстуры осуществляется, как правило, в теле функции вывода (рендеринга).

    C++device->SetTexture ( 0, tex );
    Pascaldevice.SetTexture( 0, tex );
    Если в сцене присутствуют несколько объектов с различными текстурами, то процесс установки текстуры и вывода примитивов должен быть последовательным:

    SetTexture ( 0, tex1 ); drawObject1();

    SetTexture ( 0, tex2 ); drawObject2();

    Для деактивации текстуры в некотором текстурном уровне достаточно вызвать метод SetTexture с нулевым (пустым) значением второго параметра.

    C++device->SetTexture ( 0, 0 );
    Pascaldevice.SetTexture( 0, nil );
    В большинстве случаях размер накладываемой текстуры и размер текстурируемого полигона (треугольника) не совпадают. Поэтому когда исходная текстура меньше чем полигон, на который она накладывается, то текстура должна быть увеличена (растянута) до размеров полигона. И наоборот, когда текстура больше чем полигон, то она должна быть уменьшена (сжата) до размеров полигона.


    Текстурирование

    Фильтрация текстур – это механизм, с помощью которого библиотека Direct3D производит наложение текстуры на полигоны отличающегося размера. Наиболее распространенными по использованию являются следующие типы фильтрации текстур:

  • точечная фильтрация (используется по умолчанию) – самая быстрая по скорости, но самая низкая по качеству;
  • линейная фильтрация – приемлемое качество и скорость;
  • анизотропная – самая медленная, но самая качественная.


  • Программно установить тот или ной тип фильтрации можно с помощью вызова метода SetSamplerState интерфейса IDirect3DDevice9:

    SetSamplerState( 0, D3DSAMP_MINFILTER, <тип фильтрации> ) SetSamplerState( 0, D3DSAMP_MAGFILTER, <тип фильтрации> ),

    где <тип фильтрации> – одна из следующих констант:

    D3DTEXF_POINT – точечная фильтрация,

    D3DTEXF_LINEAR – линейная фильтрация,

    D3DTEXF_ANISOTROPIC – анизотропная фильтрация;

    константы D3DSAMP_MAGFILTER и D3DSAMP_MINFILTER указывают на то, что размер текстуры меньше и больше размеров полигона соответственно.

    Как правило, функции установки типа фильтрации для двух упомянутых выше случаев вызывают совместно. Ниже приведен пример фильтрации текстур с точечной и линейной интерполяцией.



    Текстурирование


    Текстурирование
    D3DTEXF_POINTD3DTEXF_LINEAR
    На самом деле значения текстурных координат могут выходить за пределы отрезка [0,1]. Как при этом будет себя "вести" текстура зависит от установленного режима адресации. Существует 4 типа адресации текстур:

  • wrap
  • border color
  • clamp
  • mirror


  • Разберем на примерах результат работы каждого типа. Пусть у нас имеется исходная текстура и квадрат, на который мы хотим наложить ее, причем вершины квадрата имеют текстурные координаты, показанные ниже:

    Текстурирование

    В таблице представлены способы установки различных режимов адресации текстур, а также показан результат их действий.



    SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP ); SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP );

    Текстурирование



    SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER ); SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER ); SetSamplerState( 0, D3DSAMP_BORDERCOLOR, D3DCOLOR_XRGB(64,64,0) );

    Текстурирование



    SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP ); SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );

    Текстурирование



    SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR ); SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR );

    Текстурирование

    <


    Вершина полигона может содержать одновременно и цвет и текстурные координаты. В этом случае итоговое значение каждого пикселя будет определяться как средневзвешенная сумма соответствующего пикселя и текселя текстуры.

    C++ struct CUSTOMVERTEX { FLOAT x, y, z, rhw; // координаты вершины DWORD color; // цвет вершин FLOAT tu, tv; // текстурные координаты }; #define MY_FVF (D3DFVF_XYZRHW | D3DFVF_ DIFFUSE | D3DFVF_TEX1)
    Pascaltype MyVertex = packed record x, y, z, rhw: Single; // координаты вершины color: DWORD; // цвет вершин u,v: Single; // текстурные координаты end;

    const MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE or D3DFVF_TEX1;
    Пример вывода квадрата, состоящего из двух треугольников, у которых вершины содержат как цвет, так и текстурные координаты показан ниже.

    Текстурирование

    Вообще говоря, правила взаимодействия, по которым два пикселя будут формировать результирующий цвет, можно явно указывать с помощью состояний текстурных уровней. Каждый текстурный уровень принимает на вход два цветовых аргумента и определяет правило (операцию) по которому будут смешиваться эти цвета. Программно это реализуется с помощью вызова метода SetTextureStageState интерфейса IDirect3DDevice9.

    Первый аргумент указывает номер текстурного уровня, второй аргумент задает название изменяемого параметра, а третий – его значение. Определить значения двух цветовых аргументов для нулевого текстурного уровня можно следующим образом:

    SetTextureStageState( 0, D3DTSS_COLORARG{1,2}, <значение> ),

    где <значение> в нашем случае может быть константой D3DTA_DIFFUSE либо D3DTA_TEXTURE. Определить операцию взаимодействия двух цветовых аргументов позволит следующий вызов:

    SetTextureStageState( 0, D3DTSS_COLOROP, <операция> ),

    где <операция> может принимать следующие значения:

    Вид операцииОписание
    D3DTOP_SELECTARG1выбор в качестве результата первого цветового аргумента, при этом значение второго аргумента в рассмотрение не берется
    D3DTOP_SELECTARG2выбор в качестве результата второго цветового аргумента, при этом значение первого аргумента в рассмотрение не берется
    D3DTOP_MODULATEпокомпонентное перемножение первого и второго цветового аргумента
    D3DTOP_MODULATE2Xпокомпонентное перемножение первого и второго цветового аргумента и битовый сдвиг на 1 бит влево (умножение на 2)
    D3DTOP_MODULATE4Xпокомпонентное перемножение первого и второго цветового аргумента и битовый сдвиг на 2 бита влево (умножение на 4)
    D3DTOP_ADDпокомпонентное сложение первого и второго цветового аргумента
    D3DTOP_SUBTRACTпокомпонентное вычитание из первого цветового аргумента второго


    Сам процесс смешивания цветов может быть описан следующей блок-схемой.

    Текстурирование

    Следует отметить, что все цветовые операции над пикселями производятся покомпонентно для каждого оттенка. Отдельно для красного, зеленого и синего цветов, причем диапазон принимаемых значений каждого цветового канала ограничен в пределах [0,1]. Пусть формат вершины содержит цвет и текстурные координаты. Ниже показаны примеры смешивания цветовой и текстурной составляющей вершины при использовании только нулевого текстурного уровня.



    D3DTSS_COLORARG1 = D3DTA_TEXTURE D3DTSS_COLORARG2 = D3DTA_DIFFUSE D3DTSS_COLOROP = D3DTOP_SELECTARG1


    Текстурирование


    D3DTSS_COLORARG1 = D3DTA_TEXTURE D3DTSS_COLORARG2 = D3DTA_DIFFUSE D3DTSS_COLOROP = D3DTOP_SELECTARG2


    Текстурирование


    D3DTSS_COLORARG1 = D3DTA_TEXTURE D3DTSS_COLORARG2 = D3DTA_DIFFUSE D3DTSS_COLOROP = D3DTOP_MODULATE


    Текстурирование


    D3DTSS_COLORARG1 = D3DTA_TEXTURE D3DTSS_COLORARG2 = D3DTA_DIFFUSE D3DTSS_COLOROP = D3DTOP_MODULATE2X


    Текстурирование


    D3DTSS_COLORARG1 = D3DTA_TEXTURE D3DTSS_COLORARG2 = D3DTA_DIFFUSE D3DTSS_COLOROP = D3DTOP_MODULATE4X


    Текстурирование


    D3DTSS_COLORARG1 = D3DTA_TEXTURE D3DTSS_COLORARG2 = D3DTA_DIFFUSE D3DTSS_COLOROP = D3DTOP_ADD


    Текстурирование


    D3DTSS_COLORARG1 = D3DTA_TEXTURE D3DTSS_COLORARG2 = D3DTA_DIFFUSE D3DTSS_COLOROP = D3DTOP_SUBTRACT


    Текстурирование


    D3DTSS_COLORARG1 = D3DTA_DIFFUSE D3DTSS_COLORARG2 = D3DTA_TEXTURE D3DTSS_COLOROP = D3DTOP_SUBTRACT


    Текстурирование
    Если вершина не содержит цветовой составляющей, то по умолчанию выбирается белый цвет для D3DTA_DIFFUSE. Эта возможность позволяет реализовать негативное преобразование изображения (текстуры).



    D3DTSS_COLORARG1 = D3DTA_DIFFUSE (белый) D3DTSS_COLORARG2 = D3DTA_TEXTURE D3DTSS_COLOROP = D3DTOP_SUBTRACT


    Текстурирование

    Буфер трафарета

    Библиотека Direct3D располагает средствами работы с так называемым буфером трафарета. Буфер трафарета представляет собой двумерный массив с размерами как у буфера кадра и z-буфера, причем пиксель с координатами (x,y) в буфере трафарета будет соответствовать пикселю с такими же координатами в буфере кадра. Буфер трафарета работает как маска (трафарет), позволяя нам блокировать вывод некоторых пикселей на экран по заданному правилу. Принцип работы с буфером трафарета – это, как правило, двухпроходный алгоритм. Сначала мы разбиваем область вывода на зоны и каждой зоне присваиваем номер, при этом ничего не выводится на экран. Затем на основании некоторого установленного правила, мы производим непосредственный вывод сцены на экран, причем одни зоны могут быть выведены, другие нет.
    По умолчанию тест трафарета выключен (заблокирован). Чтобы его включить необходимо установить значение переменной D3DRS_STENCILENABLE в true. Программно это выглядит следующим образом:
    C++
    device->SetRenderState( D3DRS_STENCILENABLE, true ); // включение device->SetRenderState( D3DRS_STENCILENABLE, false ); // выключение
    Pascal
    device.SetRenderState( D3DRS_STENCILENABLE, 1 ); // включение device.SetRenderState( D3DRS_STENCILENABLE, 0 ); // выключение

    Очистка буфера трафарета осуществляется с помощью вызова метода Clear интерфейса IDirect3DDevice9, который использовался нами и для очистки буфера кадра.
    C++
    device->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0f, 0 );
    Pascal
    device.Clear( 0, nil, D3DCLEAR_TARGET or D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0, 0 );

    Добавленная константа (D3DCLEAR_STENCIL) в третий аргумент метода указывает, что мы собираемся очищать еще и буфер трафарета. А последний параметр метода (в нашем случае он ноль) определяет значение, которым будет заполнен буфер трафарета.
    В силу того, что буфер трафарета может работать только совместно с буфером глубины, нам придется сказать несколько слов и о нем. Буфер глубины или z-буфер – это вспомогательный "экран" необходимый для удаления невидимых наблюдателю граней и поверхностей.
    Буфер глубины представляет собой двумерный массив, хранящий z-координаты каждого пикселя. Программно буфер глубины может быть инициализирован с помощью заполнения двух полей структуры D3DPRESENT_PARAMETERS:

    C++

    D3DPRESENT_PARAMETERS params; … ZeroMemory( ¶ms, sizeof(params) ); … params.EnableAutoDepthStencil = true; params.AutoDepthStencilFormat = D3DFMT_D16; …
    Pascal

    var params: TD3DPresentParameters; … ZeroMemory( @params, SizeOf(params) ); … params.EnableAutoDepthStencil := true; params.AutoDepthStencilFormat := D3DFMT_D16; …
    Установка поля EnableAutoDepthStencil в значение true разрешает работы с буферами глубины и трафарета. Поле AutoDepthStencilFormat задает их формат. В приведенном выше примере задается формат только для буфера глубины, который имеет 16-ти битный формат памяти.

    Буфер трафарета может быть создан в тот же момент, когда создается буфер глубины. Определяя формат буфера глубины, мы можем указать формат и для трафаретного буфера. Z-буфер и буфер трафарета представляют собой внеэкранные поверхности одинакового размера но разного формата.

    Ниже приведены константы, которые позволяют задать формат буфера глубины и трафарета совместно:

    D3DFMT_D24S8буфер глубины определяется 24 битами на пиксель; трафаретный буфер задан 8-ю битами на пиксель.
    D3DFMT_D15S1буфер глубины определяется 15 битами на пиксель; трафаретный буфер задан одним битом на пиксель.
    D3DFMT_D24X4S4буфер глубины определяется 24 битами на пиксель; трафаретный буфер задан 4-мя битами на пиксель и 4 бита не используются
    Следует отметить, что не все видеокарты могут поддерживать 8-ми битный буфер трафарета.

    Как мы поняли, трафаретный буфер позволяет блокировать вывод некоторых пикселей и регионов в буфере кадра. Это достигается с помощью так называемого теста трафарета, который задает функцию сравнения значения находящего в буфере трафарета (value) с некоторым заданным (ref). Эта идея может быть описана следующим псевдоматематическим выражением:

    (ref & mask) ОперацияСравнения (value & mask),


    где символ "&" означает побитовую операцию AND,

    mask – некоторая заданная маска сравнения, которая может быть использована для того чтобы скрыть (выключить) некоторые биты одновременно в двух параметрах: value и ref (по умолчанию mask = 0xffffffff).

    Изменить значение маски трафарета можно следующим образом:

    C++device->SetRenderState( D3DRS_STENCILMASK, 0xff00ffff );
    Pascaldevice.SetRenderState( D3DRS_STENCILMASK, $ff00ffff );
    Значение, задаваемое параметром ref по умолчанию равно нулю. Изменить его можно с помощью следующих строк:

    C++device->SetRenderState( D3DRS_STENCILREF, 1 );
    Pascaldevice.SetRenderState( D3DRS_STENCILREF, 1 );
    Тест трафарета сравнивает два значения (ref & mask) и (value & mask), и если результат сравнения выдает истину, то тест считается успешно пройденным. В качестве операции сравнения могут выступать следующие 8 предопределенных констант:

    КонстантаТест трафарета завершается успешно в случае …
    D3DCMP_NEVERНикогда
    D3DCMP_LESSЕсли (ref & mask) < (value & mask)
    D3DCMP_EQUALЕсли (ref & mask) = (value & mask)
    D3DCMP_LESSEQUALЕсли (ref & mask) <= (value & mask)
    D3DCMP_GREATERЕсли (ref & mask) > (value & mask)
    D3DCMP_NOTEQUALЕсли (ref & mask) <> (value & mask)
    D3DCMP_GREATEREQUALЕсли (ref & mask) >= (value & mask)
    D3DCMP_ALWAYSВсегда
    Задать нужную операцию сравнения с помощью следующего вызова:

    C++device->SetRenderState( D3DRS_STENCILFUNC, <операция сравнения> );
    Pascaldevice.SetRenderState( D3DRS_STENCILFUNC, <операция сравнения> );
    где <операция сравнения> - одна из перечисленных в таблице констант.

    Если тест трафарета завершился неудачно, то для данных пикселей происходит блокировка записи в буфер кадра. Если же тест трафарета проходит успешно, то пиксели записываются в буфер кадра.

    Библиотека Direct3D позволяет определить действия, которые будут выполнены в случае:

  • если тест трафарета завершился неудачно;
  • если тест трафарета прошел успешно, а тест глубины завершился отрицательно;
  • если и тест трафарета, и тест глубины завершились успешно.



  • Все эти действия связаны с обновлением буфера трафарета по определенному правилу. Задать какое действие будет произведено в том или ином случае можно следующим образом:

    SetRenderState( D3DRS_STENCILFAIL, <действие> ); SetRenderState( D3DRS_STENCILZFAIL, <действие>);

    SetRenderState( D3DRS_STENCILPASS, <действие>);

    В качестве <действие> может выступать одна из предопределенных констант:

    КонстантаДействие
    D3DSTENCILOP_KEEPНе изменять значение в буфере трафарета
    D3DSTENCILOP_ZEROУстановить значение в буфере трафарета в ноль
    D3DSTENCILOP_REPLACEЗаменить значение в буфере трафарета на значение ref, определенное константой D3DRS_STENCILREF
    D3DSTENCILOP_INCRSATУвеличить значение буфера трафарета на единицу
    D3DSTENCILOP_DECRSATУменьшить значение буфера трафарета на единицу
    D3DSTENCILOP_INVERTПроизвести операцию побитового инвертирования
    D3DSTENCILOP_INCRУвеличить значение буфера трафарета на единицу
    D3DSTENCILOP_DECRУменьшить значение буфера трафарета на единицу
    Различие между константами D3DSTENCILOP_INCRSAT и D3DSTENCILOP_INCR заключается в том, что при выходе за границу допустимого диапазона значений, в первом случае результат будет приведен к максимальному значению, а во втором – нулю. Аналогично с константами D3DSTENCILOP_DECRSAT и D3DSTENCILOP_DECR: в случае выхода за левую границу интервала (ноль), результат будет приведен к нулю, а во втором случае к максимальному значению.

    В качестве применения буфера трафарета рассмотрим пример "вырезания" одной плоской фигуры из другой. Пусть нам необходимо вырезать один треугольник (желтый) из другого (разноцветный), при этом там, где они не перекрываются, должен быть виден квадрат, покрытый текстурой.

    Буфер трафарета

    Алгоритмически шаги, достижения представленного результата можно записать так:



    Шаг 1.

    Очистка буфера кадра и буфера трафарета (значение для фона в буфере трафарета - ноль)

    device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255,255,255), 0.0f, 0);



    Шаг 2.

    Вывод квадрата, покрытого текстурой (тест трафарета при этом отключен)

    … device->SetTexture(0, tex); device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2); …



    Шаг 3.

    3.1 Включаем (разрешаем) тест трафарета.

    3.2. В качестве параметра прохождения теста трафарета установить D3DCMP_NEVER (тест никогда не проходит и, следовательно, примитив не будет записан в буфер кадра).

    3.3. В качестве значения записать в буфер трафарета, например, число два.

    3.4. В качестве действия отрицательного прохождения теста трафарета установить D3DSTENCILOP_REPLACE (в буфере трафарета будет записана "двойка" на месте вырезки)

    3.5. Вызвать функцию отрисовки желтого треугольника (сам треугольник не будет нарисован, но буфер трафарета обновит свое содержимое)

    … device->SetRenderState(D3DRS_STENCILENABLE, true); device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_NEVER); device->SetRenderState(D3DRS_STENCILREF, 0x2); device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE); device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); …



    Шаг 4.

    4.1. В качестве параметра прохождения теста трафарета установить D3DCMP_GREATER

    4.2. В качестве значения, с которым будет сравниваться содержимое буфера трафарета, установить, например, число один.

    4.3. Вывести разноцветный треугольник

    … device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ GREATER); device->SetRenderState(D3DRS_STENCILREF, 0x1); device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_REPLACE); device->DrawPrimitive(D3DPT_TRIANGLELIST, 3, 1); …

    <


    Таким образом, содержимое буфера трафарета будет выглядеть следующим образом:

    Буфер трафарета

    Зеленый цвет соответствует значению ноль в буфере трафарета (фон), желтый – значению два, красный – значению один (разноцветный треугольник).

    Как правило, буфер трафарета используется в двух- и более проходных алгоритмах построения сцены. Когда вначале формируется содержимое буфера трафарета, а затем на его основании выводится вся сцена. При этом на первом этапе алгоритма может сформироваться некое содержимое буфера кадра. Обычно затем следует операция очистки буфера кадра и буфера глубины, но не буфера трафарета. А затем выполняется второй шаг, непосредственно вывод примитивов. Алгоритмически это может выглядеть приблизительно следующим образом:

  • Очистка буферов глубины, кадра и трафарета;
  • Включение теста трафарета и маскирование части экрана с помощью операций с буфером трафарета (при этом для тех пикселей, которые прошли тест трафарета успешно, в буфере кадра будет сформирован некий образ);
  • Очистка буферов глубины и кадра;
  • Вывод всей сцены с учетом трафаретной маски.


  • Буфер трафарета позволяет также реализовывать множество других эффектов, таких как построение теней и зеркальных плоскостей. С ними мы познакомимся в разделе трехмерной графики. Буфер трафарета

    Цветовой ключ

    В библиотеке Direct3D имеется функция D3DXCreateTextureFromFileEx(), которая позволяет задать так называемый цветовой ключ (color key). Механизм цветового ключа заключается в том, что оговаривается заданный цвет, который становится прозрачным при выводе текстуры на некоторую поверхность. Данная функция имеет 14 параметров, но нам для рассмотрения примера потребуется всего 4, для остальных параметров зададим значения по умолчанию. Первый параметр функции представляет собой указатель на устройство вывода, второй содержит имя загружаемого графического файла текстуры, одиннадцатый параметр и представляет собой цветовой ключ в формате ARGB, причем значение альфа составляющей должно обязательно быть 255, и последний параметр – указатель на переменную, в которую будет возвращен результат вызова. Ниже приведен пример вызова этой функции.
    C++
    D3DXCreateTextureFromFileEx(device, "tree.bmp", D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, D3DCOLOR_ARGB(255,0,0,0), NULL, NULL, &tex);
    Pascal
    D3DXCreateTextureFromFileEx(device, 'tree.bmp', D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, D3DCOLOR_ARGB(255,0,0,0), nil, nil, tex);

    После загрузки текстуры с цветовым ключом необходимо включить режим полупрозрачности и выставить необходимые параметры смешивания цветов:
    device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    Ниже приведен пример вывода текстуры, в которой в качестве цветового ключа объявлен черный цвет. Вывод специально производится на другую текстуру, чтобы продемонстрировать работу механизма цветовых ключей в действии.
    Цветовой ключ

    Полупрозрачность

    Рассмотрим далее реализацию такого эффекта как полупрозрачность (alpha blending). Этот механизм позволяет задавать для выводимых примитивов прозрачные и полупрозрачные пиксели. Т.е. существует возможность указать, какие пиксели не будут выводиться в силу своей полной прозрачности, а какие будут выводиться частично прозрачными. Полупрозрачность основывается на принципе смешивания цветов. Математически это можно выразить так: ColorResult = Color1*(1-t) + Color2*t, 0<=t<=1. Эта векторная запись должна интерпретироваться для каждого отдельного цветового канала. Пусть ColorResult = (r, g, b), Color1 = (r1, g1, b1), Color2 = (r2, g2, b2), тогда
    r = r1*(1-t) + r2*t g = g1*(1-t) + g2*t b = b1*(1-t) + b2*t, 0<=t<=1.
    Ниже приведен пример смешивания двух цветов (красного и зеленого) для различных значений параметра t (Color1 = красный = (255,0,0) Color2 = зеленый = (0,255,0)).

    Полупрозрачность

    Полупрозрачность

    Полупрозрачность

    Полупрозрачность

    Полупрозрачность

    Полупрозрачность

    Полупрозрачность

    Полупрозрачность

    Полупрозрачность
    t=0t=1/8t=2/8t=3/8t=4/8t=5/8t=6/8t=7/8t=1

    При работе с полупрозрачностью, как правило, имеют дело (оперируют) с двумя цветами: цвет источника (source color) – это пиксель, который вы собираетесь отобразить (записать в буфер кадра) и цвет приемника (destination color) – это пиксель, который уже существует и записан в буфере кадра. Другими словами можно сказать, что цвет источника (source color) – "пиксель который рисуем", а цвет приемника (destination color) – "пиксель на котором рисуем". Механизм полупрозрачности использует следующую формулу для управления степенью полупрозрачности:
    Финальный цвет = Цвет пикселя источника * Коэффициент прозрачности источника + Цвет пикселя приемника * Коэффициент прозрачности приемника.
    Программист может управлять коэффициентами прозрачности с помощью флагов:
    коэффициент прозрачности источника - D3DRS_SRCBLEND,
    коэффициент прозрачности приемника - D3DRS_DESTBLEND и используя формулу FinalPixel = SourcePixelColor*SourceBlendFactor + DestPixelColor*DestBlendFactor.
    Механизм полупрозрачности по умолчанию выключен как опция.
    Активировать/деактивировать полупрозрачность можно следующим образом:

    C++device->SetRenderState( D3DRS_ALPHABLENDENABLE, {TRUE, FALSE} );
    Pascaldevice.SetRenderState( D3DRS_ALPHABLENDENABLE, {1, 0} );
    Установка коэффициентов смешивания (SourceBlendFactor и DestBlendFactor) осуществляется следующим образом:

    SetRenderState( D3DRS_SRCBLEND, SourceBlendFactor ), SetRenderState( D3DRS_DESTBLEND, DestBlendFactor ),

    где в качестве SourceBlendFactor и DestBlendFactor могут выступать предопределенные константы:

    D3DBLEND_ZERO—blendFactor=(0,0,0,0) D3DBLEND_ONE—blendFactor=(1,1,1,1) D3DBLEND_SRCCOLOR—blendFactor=(Rs, Gs, Bs, As) D3DBLEND_INVSRCCOLOR—blendFactor=(1–Rs, 1–Gs, 1–Bs, 1–As) D3DBLEND_SRCALPHA—blendFactor=(As, As, As, As) D3DBLEND_INVSRCALPHA—blendFactor=(1–As, 1–As, 1–As, 1–As) D3DBLEND_DESTALPHA—blendFactor=(Ad, Ad, Ad, Ad) D3DBLEND_INVDESTALPHA—blendFactor=(1–Ad, 1–Ad, 1–Ad, 1–Ad) D3DBLEND_DESTCOLOR—blendFactor=(Rd, Gd, Bd, Ad) D3DBLEND_INVDESTCOLOR—blendFactor=(1–Rd, 1–Gd, 1–Bd, 1–Ad) D3DBLEND_SRCALPHASAT—blendFactor=(f,f,f,1), где f=min(As, 1–Ad). По умолчанию SourceBlendFactor = D3DBLEND_SRCALPHA DestBlendFactor = D3DBLEND_INVSRCALPHA.

    Рассмотрим несколько примеров использования механизма полупрозрачности.

    Пример 1.

    Если мы не хотим ничего "смешивать", тогда можно воспользоваться такой формулой:

    FinalPixel = SourcePixelColor*1 + DestPixelColor*0.

    Этого можно достичь, выставив следующие значения коэффициентов смешивания:

    SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); SetRenderState( D3DRS_DESTBLEND,D3DBLEND_ZERO );

    Пример 2.

    Чтобы происходило "сложение" цветов, можно использовать такую формулу:

    FinalPixel = SourcePixelColor*1 + DestPixelColor*1.

    Для этого, оба коэффициента смешивания выставим в единицу:

    SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );

    Ниже приведен визуальный пример такого "сложения" цветов.

    Полупрозрачность

    Следует отметить, что примитивы при полупрозрачности могут быть не только однотонными.


    Ниже показаны примеры "умножения" цветов для примитивов, с плавными переходами цветов.

    Полупрозрачность

    Полупрозрачность

    Пример 3.

    Эффект "перемножения" цветов примитивов можно получить с помощью формулы

    FinalPixel = SourcePixelColor*0 + DestPixelColor*SourcePixelColor.

    Значения коэффициентов смешивания выставим следующим образом:

    SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR );

    Ниже показаны примеры подобного "умножения" цветов при различном фоне.



    Полупрозрачность


    Полупрозрачность
    цвет фона D3DCOLOR_XRGB(0,0,0)цвет фона D3DCOLOR_XRGB(255,255,255)
    Прозрачность может "содержаться" и в вершинах примитивов. В дополнение к красной (red), зеленой (green) и синей (blue) цветовым составляющим, каждый пиксель может иметь "прозрачную" составляющую или так называемый альфа канал (alpha channel). Альфа канал имеет 256 уровней прозрачности, 0…255:

    0 – пиксель полностью прозрачен

    255 – пиксель полностью непрозрачен

    128 – пиксель прозрачен наполовину.

    До сих пор цвет мы определяли с помощью тройки чисел RGB, используя макрос-функцию D3DCOLOR_XRGB(). Для задания значения альфа составляющей вершины можно воспользоваться функцией D3DCOLOR_ARGB(a, r, g, b), где первый параметр определяет значение полупрозрачности. Следует заметить, что значение альфа канала (как и всех цветовых составляющих) приводится к диапазону значений [0…1]. Ниже приводится пример заполнения вершин примитивов данными о цвете со значением полупрозрачности 50 процентов.

    C++

    points[0].color = D3DCOLOR_ARGB( 128, 255, 0, 0 ); …
    Pascal

    points[0].color := D3DCOLOR_ARGB( 128, 255, 0, 0 ); …
    Использовать полупрозрачность в вершинах примитивов можно с помощью все той же формулы

    FinalPixel = SourcePixelColor*SourceBlendFactor + DestPixelColor*DestBlendFactor,

    при этом задействовав константы смешивания с суффиксом ALPHA. Так, например, следующие установки коэффициентов смешивания

    SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );


    преобразуют формулу к виду

    FinalPixel = SourcePixelColor*(Alpha) + DestPixelColor*(1- Alpha),

    где Alpha – значение альфа составляющей пикселя-источника.

    В качестве примера разберем вывод одного полупрозрачного примитива на непрозрачном. Пусть имеется два треугольника, красный и синий, показанные ниже, причем первый из них не содержит значений полупрозрачности, а у второго все вершины имеют полупрозрачность – 50 процентов.



    Полупрозрачность


    Полупрозрачность
    D3DCOLOR_XRGB(255,0,0);D3DCOLOR_ARGB(128,0,0,255);
    Последовательность вывода примитивов будет такой:

  • Первым рисуется красный треугольник (полупрозрачность выключена);
  • Включаем полупрозрачность: SetRenderState(D3DRS_ALPHABLENDENABLE, 1);
  • Рисуем синий треугольник (в вершинах задана 50 % полупрозрачность);


  • В результате формула смешивания примет вид: FinalPixel = Синий*0.5 + Красный*(1-0.5).

    Полупрозрачность

    Ниже приведены примеры вывода примитивов с различной степенью полупрозрачности.



    Полупрозрачность


    Полупрозрачность


    Полупрозрачность
    ARGB = (32,0,0,255)ARGB = (64,0,0,255)ARGB = (128,0,0,255)


    Полупрозрачность


    Полупрозрачность


    Полупрозрачность
    ARGB = (160,0,0,255)ARGB = (192,0,0,255)ARGB(255,0,0,255)
    Значения полупрозрачности для каждой вершины примитива могут отличаться друг от друга. В этом случае значения полупрозрачности для внутренних точек треугольника будут линейно проинтерполированы. Такой случай представлен ниже.

    Полупрозрачность

    Полупрозрачность можно использовать не только на цветных примитивах. Ниже показан пример полупрозрачного треугольника на фоне квадрата, покрытого текстурой.

    Последовательность вывода примитивов должна быть следующей:

  • Вначале выводится квадрат с текстурой, при этом полупрозрачность выключена;
  • Включается режим полупрозрачности с нужными коэффициентами смешивания;
  • Выводится цветной треугольник поверх квадрата.


  • Полупрозрачность

    Информация о полупрозрачности пикселей может содержаться и в самой текстуре. В этом случае для альфа составляющей отводится, как правило, такое же количество бит, что и под каждый из цветовых каналов. Наиболее распространенный формат представления изображения с альфа-каналом это 32 битные изображения, где на каждый канал отводится по 8 бит.


    Для создания полупрозрачной текстуры можно воспользоваться утилитой DirectX Texture Tool, которая поставляется совместно с DirectX SDK. Сам альфа-канал можно создать в виде изображения в оттенках серого цвета, в котором абсолютно черные пиксели будут соответствовать полной прозрачности, а белые пиксели – полной непрозрачности.

    По умолчанию если текстура содержит альфа канал, то значение альфа составляющей берется из текстуры. Если же альфа канал не присутствует в текстуре, то значение альфа составляющей будет получено из вершины примитива. Тем не менее, можно явно указать "источник" альфа канала:

    // вычисление альфа значения из текстуры device->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); device->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );

    // вычисление альфа значения из вершины примитива device->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ); device->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );

    Ниже приведен пример вывода полупрозрачной текстуры.



    Полупрозрачность


    Полупрозрачность


    Полупрозрачность
    Текстура (RGB канал)Альфа каналРезультат
    Полупрозрачность в текстурах позволяет реализовать идею вывода спрайтов – небольших изображений без фона. Для этого достаточно взять изображение спрайта и добавить в него альфа канал. Те пиксели, которые считаются фоном должны быть черными в альфа канале, а которые относятся к спрайту – белыми.

    Ниже приведен пример вывода спрайтов с помощью текстур, содержащих альфа канал.



    Полупрозрачность


    Полупрозрачность


    Полупрозрачность


    Полупрозрачность


    Полупрозрачность


    Полупрозрачность


    Полупрозрачность


    Полупрозрачность


    Полупрозрачность
    Исходное изображениеАльфа каналВывод на текстуру
    В некотором смысле, альфа канал здесь используется в качестве маски вывода, при помощи которой происходит "отбрасывание" ненужных (фоновых) пикселей.

    Построение отрезков

    Библиотека Direct3D имеет в своем составе средства для построения связных отрезков, которые кроме всего прочего имеют толщину и стиль. Для вывода подобных примитивов (линий) предусмотрен интерфейс ID3DXLine. Для начала необходимо объявить нужные переменные интерфейсного типа.
    C++ID3DXLine *line = NULL;
    Pascalvar line: ID3DXLine;

    Создание объекта "линия" осуществляется с помощью вызова функции D3DXCreateLine, которая имеет два параметра: первый – указатель на устройство вывода, второй адрес переменной, в которую запишется результат.
    C++D3DXCreateLine(&device, &line);
    PascalD3DXCreateLine(device, line);

    Интерфейс ID3DXLine содержит несколько методов для работы для рисования линий. Для вывода обычного отрезка на экран необходимо задать координаты его концевых точек. Объявим дополнительную переменную-массив, в которой и будет храниться эта информация.
    C++D3DXVECTOR2 points[] = { (100.0f, 100.0f), (200.0f, 200.0f) };
    Pascal varpoints: array[0..1] of TD3DXVector2 = ( (x:100; y:100), (x:200; y:200) );

    Тип D3DXVECTOR2 представляет собой запись из двух вещественных полей, описывающих точку (вектор) в двумерном пространстве.
    Непосредственный вывод отрезка заключается в вызове метода Draw интерфейса ID3DXLine. Данный метод имеет три параметра: первый – указатель на массив точек, второй аргумент определяет их количество, а третий – цвет выводимой линии.
    C++line->Draw(points, 2, D3DCOLOR_XRGB(255,255,0));
    Pascalline.Draw(@points, 2, D3DCOLOR_XRGB(255,255,0));

    Принято вызов Draw обрамлять вызовами методов Begin и End до и после соответственно.
    Метод Begin подготавливает устройство вывода к процессу формирования линий, а метод End восстанавливает состояние устройства вывода в изначальное.
    C++
    line->Begin(); line->Draw(…); line->End();
    Pascal
    line._Begin; line.Draw(…); line._End;

    Наличие лидирующего символа подчеркивание у методов Begin и End в синтаксисе Паскаля обусловлено тем, что они являются ключевыми словами в данном языке.
    Для установки нужной ширины рисуемой линии интерфейс ID3DXLine обладает методом SetWidth(), в качестве параметра которому передается значение ширины линии в пикселях.
    Например, вывод отрезка прямой линии толщиной 5 пикселей осуществляется с помощью такого кода:

    C++

    … line->SetWidth(5.0f); line->Draw(points, 2, D3DCOLOR_XRGB(255,255,0));
    Pascal

    … line.SetWidth(5); line.Draw(@points, 2, D3DCOLOR_XRGB(255,255,0));
    Ниже приведены примеры вывода отрезков с шириной.



    Построение отрезков


    Построение отрезков
    line->SetWidth(1.0f);line->SetWidth(3.0f);


    Построение отрезков


    Построение отрезков
    line->SetWidth(5.0f);line->SetWidth(10.0f);
    Для устранения лестничного эффекта (алиасинга), возникающего при растеризации отрезков, расположенных под углом к горизонту, можно воспользоваться методом SetAntialias интерфейса ID3DXLine, который имеет один параметр булевского типа. Значение true указывает на включение режима антиалиасинга, false – выключение режима. Ниже приведен пример построения отрезка прямой с включенным режимом антиалиасинга и без него.



    Построение отрезков
    line->SetAntialias(false);


    Построение отрезков
    line->SetAntialias(true);
    В силу того, что в двухмерной графике плоские кривые при выводе на растр представляются в виде аппроксимирующих ломаных линий (в виде последовательности небольших отрезков), то использование интерфейса ID3DXLine представляется простым и удобным способом отображения непрерывных двумерных примитивов. Ниже приводится пример построения элементарной кривой Безье третьего порядка путем кусочно-линейной аппроксимации с различной степенью точности. В данном случае точность определяется количеством ломаных линий (N), из которых строится кривая.

    N=4

    Построение отрезков
    N=5

    Построение отрезков
    N=7

    Построение отрезков
    N=10

    Построение отрезков
    N=20

    Построение отрезков

    Спрайты

    В составе библиотеки имеется богатый набор методов для работы со спрайтами – небольшие изображения, в которых "отсутствует" фон. Спрайтовый подход очень широко распространен в двумерной графике и компьютерных играх для создания различной анимации и движения. Вообще спрайт можно рассматривать как объект с абсолютно прозрачным фоном. С помощью спрайтов очень удобно изображать невыпуклые объекты сцены (с произвольной формой): деревья, огонь, дым, людей и т.д. Причем спрайты могут быть не просто отображены на экране как прозрачнее текстуры. Спрайты могут быть выведены повернутыми на определенный угол, промасштабированы и смещены на нужный вектор.
    Работу со спрайтами можно производить через вызовы методов интерфейса ID3DXSprite.
    C++LPD3DXSPRITE sprite = NULL;
    Pascalvar sprite: ID3DXSprite;

    Создание самого объекта производится с помощью вызова функции D3DXCreateSprite, которая имеет два параметра: первый – указатель на устройство вывода, второй – переменная, в которую будет помещен результат.
    C++D3DXCreateSprite(device, &sprite);
    PascalD3DXCreateSprite(device, sprite);

    Для отображения спрайта, как правило, прибегают к такому коду:
    sprite->Begin(...) sprite->Draw(...) sprite->End()
    Сам вывод спрайта сопряжен с взаимодействием с текстурой. Само изображение спрайта должно храниться в объекте LPDIRECT3DTEXTURE9 (загрузка изображения осуществляется через функцию D3DXCreateTextureFromFile), а вывод его производится через вызов метода Draw интерфейса ID3DXSprite. Данный метод содержит 5 параметров: первый – указатель на текстуру, в которой хранится изображение спрайта, второй определяет прямоугольник вырезки на текстуре (если значение равно NULL, то выводится вся текстура), третий определяет точку, вокруг которой может быть повернут спрайт (если значение NULL, то поворот будет производиться вокруг левого верхнего угла), четвертый параметр задает смещение спрайта относительно верхнего левого угла в экранных координатах, и пятый параметр определяет значение цвета, на который будет умножаться каждый пиксель спрайта (значение 0xFFFFFFFF позволяет оставить значение текселя без изменений).

    Простейший пример вывода спрайта может выглядеть следующим образом:

    C++

    // создание и загрузка спрайта D3DXCreateTextureFromFile(device, "sprite.dds", &tex); D3DXCreateSprite(device, &sprite); … // вывод спрайта на экран sprite->Begin(D3DXSPRITE_ALPHABLEND); sprite->Draw(tex, NULL, NULL, NULL, D3DCOLOR_XRGB(255,255,255)); sprite->End();
    Pascal

    // создание и загрузка спрайта D3DXCreateTextureFromFile(device, 'sprite.dds', tex); D3DXCreateSprite(device, sprite); … // вывод спрайта на экран sprite._Begin(D3DXSPRITE_ALPHABLEND); sprite.Draw(tex, nil, nil, nil, D3DCOLOR_XRGB(255,255,255)); sprite._End;
    Константа D3DXSPRITE_ALPHABLEND в качестве параметра метода Draw указывает на то, что выводится спрайт с прозрачным фоном.

    Для масштабирования и вращения спрайтов необходимо использовать матрицы. Интерфейс ID3DXSprite предоставляет метод SetTransform(), где в качестве параметра передается матрица преобразования. Чтобы определить матрицу преобразования можно воспользоваться функцией библиотеки Direct3D – D3DXMatrixAffineTransformation2D(). Данная функция имеет пять параметров: первый определяет матрицу, в которую будет помещен результат, второй параметр определяет коэффициент масштабирования, третий задает точку вращения, четвертый – угол поворота в радианах, пятый параметр определяет вектор смещения спрайта. Следует отметить, что вначале производится операция масштабирования, затем поворот и последний шаг смещение спрайта. Ниже приведен пример вывода спрайт, который увеличен в размерах в два раза по каждой оси, повернут вокруг своего центра на угол 30 градусов и смещен на вектор (150, 100). Пусть размеры спрайта будут 256х256 пикселей.

    C++

    // объявление переменных D3DXVECTOR2 rot = D3DXVECTOR2(128.0f, 128.0f); D3DXVECTOR2 trans = D3DXVECTOR2(150.0f, 100.0f); D3DXMATRIX mat;

    // функция вывода спрата D3DXMatrixAffineTransformation2D(&mat, 2.0f, &rot, 30.0f*pi/180.0f, &trans);

    Sprite->Begin(D3DXSPRITE_ALPHABLEND); sprite->SetTransform(&mat); sprite->Draw(tex, NULL, NULL, NULL, D3DCOLOR_XRGB(255,255,255)); sprite->End();
    Pascal

    // объявление переменных var rot, trans,scale: TD3DXVector2; mat: TD3DXMatrix;

    // функция вывода спрата rot:=D3DXVector2(128,128); trans:=D3DXVector2(150,100); D3DXMatrixAffineTransformation2D(mat, 2, @rot, 30*pi/180, @trans);

    sprite._Begin(D3DXSPRITE_ALPHABLEND); sprite.SetTransform(mat); sprite.Draw(tex, nil, nil, nil, D3DCOLOR_XRGB(255,255,255)); sprite._End;
    <


    Ниже показаны примеры вывода спрайта с различными углами поворота.

    Спрайты

    Для создания анимации на основе спрайтов довольно часто прибегают к следующему подходу. Используют одну текстуру, на которой представлен объект в различных положениях анимации (кадрах). Мы можем использовать второй параметр метода Draw, который позволяет определить прямоугольную область на текстуре для вывода нужного кадра анимации. Ниже приведен пример подобной текстуры с 30 кадрами анимации.

    Спрайты

    В данном примере каждый кадр имеет размеры 64х64 пикселей. Для того чтобы отобразить кадр с номером i, i=1,…,30, необходимо на исходной текстуре вырезать прямоугольник размерами 64х64 пикселей с координатами левого верхнего угла X = ((i-1) mod 5)*64, Y = ((i-1) div 5)*64, где mod – операция "остаток от отделения", а div – операция "деление без остатка". Так, например, чтобы вывести кадр номер 14, нужно вырезать прямоугольник размером 64х64 с координатами левого верхнего угла (192, 128). Программно вывод нужного кадра (frame) можно реализовать следующим образом:

    C++

    RECT r; int x0,y0; … x0 = ((frame-1) % 5)*64; y0 = ((frame-1) / 5)*64; r.left = x0; r.top = y0; r.right = x0+63; r.bottom = y0+63; sprite->Begin(D3DXSPRITE_ALPHABLEND); sprite->Draw(tex, &r, NULL, NULL, D3DCOLOR_XRGB(255,255,255)); sprite->End();
    Pascal

    var r: TRect; x0,y0: Integer; … x0:=((frame-1) mod 5)*64; y0:=((frame-1) div 5)*64; r:=Rect(x0,y0,x0+63,y0+63); sprite._Begin(D3DXSPRITE_ALPHABLEND); sprite.Draw(tex, @r, nil, nil, D3DCOLOR_XRGB(255,255,255)); sprite._End;
    Таким образом, можно сделать вывод, что функции для работы со спрайтами в библиотеке Direct3D позволяют использовать их в различных областях двумерной графики.

    Вывод сцены в текстуру

    До сих пор мы производили отображение (вывод) всей сцены непосредственно на форму. Вообще многие графические библиотеки, в том числе и Direct3D, позволяет перенаправить весь вывод не на экран, а в текстуру. С одной стороны этот механизм является довольно простым и понятным, а с другой – очень мощным, позволяющим создавать различные эффекты, среди которых построение зеркальных поверхностей и теней. Вывод в текстуру представляет собой некое расширение вывода на поверхность. Вначале мы должны объявить в программе два объекта – саму текстура и связанную с ней поверхность вывода. Это реализуется следующим образом:
    C++
    LPDIRECT3DTEXTURE9 RenderTexture = NULL; LPDIRECT3DSURFACE9 RenderSurface = NULL;
    Pascal
    var RenderTexture: IDirect3DTexture9; RenderSurface: IDirect3DSurface9;

    Следующий шаг заключается в создании текстуры. Это можно проделать, воспользовавшись, к примеру, функцией D3DXCreateTexture(). Первый аргумент функции это ссылка на устройство вывода; второй и третий параметры задают ширину и высоту создаваемой текстуры соответственно; четвертый аргумент определяет количество мип- уровней (в нашем случае мип-уровни не используются, поэтому передаем ноль); пятый параметр – флаги использования (в нашем случае необходимо передать константу D3DUSAGE_RENDERTARGET, указывающую на то, что текстура будет использоваться как поверхность вывода); шестой аргумент задает пиксельный формат текстуры (здесь, как правило, наиболее часто используются константы D3DFMT_R8G8B8, D3DFMT_A8R8G8B8, D3DFMT_R5G6B5 и др.); седьмой параметр – это флаг месторасположения (хранения) текстуры (здесь передаем значение по умолчанию); и последний восьмой аргумент функции – это имя переменной, в которую будет возвращен результат. Ниже приводи тся пример создания текстуры размером 512х512 пикселей.
    C++
    D3DXCreateTexture(device, 512, 512, 0, D3DUSAGE_RENDERTARGET, D3DFMT_R8G8B8, D3DPOOL_DEFAULT, &RenderTexture);
    Pascal
    D3DXCreateTexture(device, 512, 512, 0, D3DUSAGE_RENDERTARGET, D3DFMT_R8G8B8, D3DPOOL_DEFAULT, RenderTexture);


    Всевозможные операции с текстурой библиотека Direct3D производит через поверхности. Для того, чтобы получить базовую поверхность любой текстуры, нужно вызвать метод GetSurfaceLevel() интерфейса IDirect3DTexture9. Метод имеет два параметра: первый – это номер текстурного уровня (так как у нас уровень всего один, то передаем ноль), а второй – указатель переменную, в которой будет храниться поверхность.
    C++RenderTexture->GetSurfaceLevel(0, &RenderSurface);
    PascalRenderTexture.GetSurfaceLevel(0, RenderSurface);

    И последний шаг в данной методике – это указание поверхности вывода. Этот шаг реализуется через вызов метода SetRenderTarget() интерфейса IDirect3DDevice9. Этот метод имеет два параметра: первый – это индекс поверхности вывода (так как у нас поверхность одна, то передаем нулевое значение); второй аргумент – поверхность вывода.
    C++device->SetRenderTarget(0, RenderSurface);
    Pascaldevice.SetRenderTarget(0, RenderSurface);

    Как правило, подобный вызов производится непосредственно в процедуре вывода примитивов перед вызовом метода Clear(). Вывод на поверхность сопряженную с текстурой ничем не отличается от обычного способа. Единственное отличие заключается в том, что результат (вся сцена) будет "воспроизводиться" в текстуре. Для того, чтобы восстановить вывод сцены обратно в буфер отображения (back buffer), нужно вначале программы сохранить указатель на этот буфер в некоторую переменную.
    C++
    LPDIRECT3DSURFACE9 BackBuffer = NULL; … device->GetRenderTarget(0, &BackBuffer);
    Pascal
    var BackBuffer: IDirect3DSurface9; … device.GetRenderTarget(0, BackBuffer);

    Затем необходимо вызвать метод SetRenderTarget(), и в качестве второго параметра передать ссылку на сохраненную поверхность вывода (back buffer).
    C++device->SetRenderTarget(0, BackBuffer);
    Pascaldevice.SetRenderTarget(0, BackBuffer);

    Поверхность вывода, ассоциированную с текстурой можно в любой момент сохранить в файл на диске. Для этого можно воспользоваться функцией D3DXSaveTextureToFile(), которая имеет четыре параметра.


    Первый аргумент – это указатель на строку, содержащую имя сохраняемого файла; второй параметр определяет формат графического файла (задается одной из следующих констант: D3DXIFF_BMP, D3DXIFF_JPG, D3DXIFF_TGA, D3DXIFF_PNG, D3DXIFF_DDS, D3DXIFF_PPM, D3DXIFF_DIB, D3DXIFF_HDR, D3DXIFF_PFM); третий аргумент – это ссылка на интерфейс IDirect3DBaseTexture9, содержащий нужную текстуру; и четвертый параметр представляет собой ссылку на структуру, определяющую палитру из 256 цветов. Ниже приведен пример сохранения поверхности вывода, ассоциированной с текстурой в файл на диске.
    C++D3DXSaveTextureToFile("scene.bmp", D3DXIFF_BMP, RenderTexture, NULL);
    PascalD3DXSaveTextureToFile('scene.bmp', D3DXIFF_BMP, RenderTexture, nil);

    Вывод сцены в текстуру

    Вывод текста

    Довольно часто возникает необходимость вывести на экран некоторый текст. Традиционно разработчики двумерных и трехмерных приложений для вывода информационных сообщений использовали следующий подход:
  • Создание текстуры, содержащей все буквы алфавита и цифры;
  • Определение для каждого символа в текстуре его положения, например, текстурные координаты;
  • Когда было нужно выводить текст, создается прямоугольник, содержащий нужный символ;
  • Устанавливаются нужные текстурные координаты, и производится вывод;
  • Эти шаги повторяются для каждого выводимого символа.

  • Библиотека Direct3D предоставляет более простые возможности по отображению текста на экране. Как обычно, вначале мы должны объявить переменные интерфейсного типа.
    C++LPD3DXFONT font;
    Pascalvar font: ID3DXFont;

    Следующий шаг – это создание самого шрифта с помощью функции D3DXCreateFont(). Данная функция имеет довольно много параметров создаваемого шрифта, но мы рассмотрим только самые необходимые. Первый параметр представляет собой ссылку на устройство вывода; второй параметр задает высоту букв шрифта (размер); третий параметр определяет ширину символов; четвертый параметр определяет, будет ли шрифт полужирным или обычным (это реализуется через предопределенные константы FW_NORMAL и FW_BOLD); пятый аргумент определяет количество мип уровней для символа (как правило, этот параметр равен нулю); шестой параметр задает, будет ли шрифт курсивный через булевскую переменную; седьмой – задает используемый набор символов (реализуется через константы, например: ANSI_CHARSET, DEFAULT_CHARSET, SYMBOL_CHARSET, RUSSIAN_CHARSET); восьмой параметр определяет, как операционная система будет выравнивать (подгонять) требуемый размер шрифта с реальным (как правило, используют константу OUT_DEFAULT_PRECIS); девятый параметр имеет смысл для растровых шрифтов и задает качество интерполяции (по умолчанию используют константу DEFAULT_QUALITY); десятый параметр определяет расстояние между символами в строке (используют константу DEFAULT_PITCH); одиннадцатый параметр определяет название шрифта, например Arial, Times New Roman, Courier New; и двенадцатый параметр – переменная (указатель), в которую будет возвращен результат вызова.
    Ниже приведен пример создания полужирного шрифта высотой 72 пикселей класса Times New Roman.
    C++
    D3DXCreateFont(device, 72, 0, FW_BOLD, 0, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, TEXT("Times New Roman"), &font);
    Pascal
    D3DXCreateFont(device, 72,0, FW_BOLD, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH , 'Times New Roman', font);

    Следующий шаг – это непосредственный вывод текста на экран. Реализуется он через вызов метода DrawText() интерфейса ID3DXFont. Метод имеет шесть параметров: первый параметр представляет собой указатель на объект ID3DXSprite (значение NULL означает, что библиотека Direct3D будет самостоятельно использовать механизм спрайтов для вывода текста); второй параметр – строка вывода; третий параметр определяет количество символов в строке (значение -1 означает, что библиотека сама подсчитает длину строки); четвертый параметр задает прямоугольную область вывода текста; пятый параметр представляет собой набор флагов, с помощью которых можно разместить выводимый текст в нужных местах (слева, справа, сверху и т.д.); шестой параметр задает цвет, которым будет выведен текст. Ниже приведен пример вывода текста, который выравнивается по правой стороне прямоугольника размерами 400х300 пикселей.
    C++
    RECT r; r.left=0; r.top=10; r.right=400; r.bottom=300; … font->DrawText(NULL, "Тверь", -1, &r, DT_RIGHT, D3DCOLOR_XRGB(255,0,0));
    Pascal
    var r: TRect; … r:=Rect(0,0,400,300); font.DrawTextA(nil, 'Тверь', -1, @r, DT_RIGHT, D3DCOLOR_XRGB(255,0,0));

    Ниже приведен пример вывода текста на поверхность, покрытую текстурой.
    Вывод текста

    Буфер глубины

    Если теперь мы попытаемся выводить объекты сцены, то они будут рисоваться в том порядке, в котором воспроизводятся. При этом нарисованные позже примитивы будут "лежать" поверх ранее выведенных. Для адекватной визуализации трехмерных объектов в графических библиотеках предусмотрен так называемый буфер глубины или z-буфер, который представляет собой двумерный массив, хранящий для каждого растеризуемого пикселя значение координаты z. Рассмотрим принцип работы буфера глубины на примере левосторонней системы координат. В начале в z-буфер заносятся (очищается) максимально возможные значения z, а буфер регенерации заполняется значениями пикселей, соответствующими фону. Затем каждая грань объекта преобразуется в растровую форму, причем порядок растеризации грани не играет особой роли. При разложении многоугольника в растр для каждой его точки выполняются следующие шаги:
  • Вычисление глубины (z-координаты) в точке (x,y);
  • Если z(x,y) меньше чем значение в z-буфере в позиции (x,y) то в z-буфер заносится значение z-координаты растеризуемой точки, а в буфер регенерации помещается обрабатываемый пиксель.

  • Если выполняется условие шага 2, это означает, что точка многоугольника расположена ближе к наблюдателю, чем точка, значение яркости которой находится в данный момент в позиции (x,y) буфера регенерации.
    Буфер глубины

    Единственный недостаток рассмотренного алгоритма – необходимость дополнительной памяти для хранения z-буфера, однако простая и эффективная реализация позволяет использовать этот алгоритм во многих приложениях. Для инициализации буфера глубины в библиотеке Direct3D достаточно заполнить два поля структуры D3DPRESENT_PARAMETERS:
    C++D3DPRESENT_PARAMETERS params; … ZeroMemory( ¶ms, sizeof(params) ); … params.EnableAutoDepthStencil = true; params.AutoDepthStencilFormat = D3DFMT_D16; …
    Pascal
    var params: TD3DPresentParameters; … ZeroMemory( @params, SizeOf(params) ); … params.EnableAutoDepthStencil := true; params.AutoDepthStencilFormat := D3DFMT_D16; …

    Программист может не беспокоиться о линейных размерах буфера глубины, т.к.
    система автоматически определяет размеры. Для работы с буфером глубины необходимо также установить константу режима D3DRS_ZENABLE в значение "истина".

    C++device->SetRenderState (D3DRS_ZENABLE, D3DZB_TRUE);
    Pascaldevice.SetRenderState(D3DRS_ZENABLE, 1);
    Чтобы отключить z-буфер, нужно установить данную константу D3DRS_ZENABLE в значение "ложь".

    C++device->SetRenderState (D3DRS_ZENABLE, D3DZB_FALSE);
    Pascaldevice.SetRenderState(D3DRS_ZENABLE, 0);
    Теперь в процессе вывода примитивов на экран, при каждом обновлении буфера кадра, необходимо производить очистку, как буфера кадра, так и буфера глубины. Это производится с помощью вызова метода Clear интерфейса IDirect3DDevice9.

    C++

    device->Clear( 0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255,255,255), 1.0f, 0 );
    Pascal

    device.Clear( 0, nil, D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255,255,255), 1.0, 0 );
    Предпоследний параметр метода Clear задает значение, которым будем заполнен буфер глубины при очистке. Значение единица соответствует бесконечно удаленной точке трехмерного пространства; значение ноль – определяет самую близкую точку.

    Принципы построения трехмерной сцены

    В трехмерной графике пространственные объекты, заданные в непрерывном виде, как правило, аппроксимируют (приближают) множеством треугольников. Именно треугольник представляет собой элементарный примитив, с помощью которого описываются все элементы сцены, в том числе и те, которые имеют гладкую форму (сфера, цилиндр, параметрические поверхности и др.).
    Процесс формирования изображения должен учитывать две главные сущности. Это визуализируемый объект сцены и наблюдатель. Объект существует в пространстве независимо от кого-либо. Наблюдатель же представляет собой средство формирования изображения наблюдаемых объектов. Именно наблюдатель формирует изображение. Наблюдатель и наблюдаемый объект существуют в одном и том же трехмерном мире, а создаваемое при этом изображение получается двухмерным. Суть процесса формирования изображения состоит в том, чтобы, зная положение наблюдателя и положение объекта, описать (синтезировать) получаемое при этом двухмерное изображение (проекцию).
    В большинстве графических библиотек присутствуют следующие сущности трехмерной сцены:
  • объекты;
  • наблюдатель (камера);
  • источники света;
  • свойства материалов объекта.

  • При этом моделируемая трехмерная сцена может описываться тысячами, а иногда миллионами вершин. Для каждой вершины (точки) примитива должны выполняться вычисления по одним и тем же формулам.
    Как мы знаем, точка в 3D графике задается, как правило, набором из 4-х значений (x,y,z,w). Реальные координаты точки в пространстве будут (x/w, y/w, z/w), компонент w является масштабом. Обычно для точек w=1. Вектор – направленный отрезок. Вектора равны если у них одинаковая длина и направление. Вектора также задаются в виде линейного массива 1х4 (x,y,z,w), но компонент w у векторов равен 0. Для выполнения преобразований с вершинами используют матричный подход. Матрица размерности MxN – прямоугольная таблица, имеющая M строк, N столбцов и заполненная элементами одного типа. В 3D графике используют, как правило, матрицы размерности 4х4. Таким образом преобразование точки в пространстве сводится к умножению вектор-строки размерности 4 на матрицу преобразования размером 4х4:

    Принципы построения трехмерной сцены

    Так, например, умножение всех вершин объекта на одну из матриц вращения приведет к вращению этого объекта вокруг оси Ox, Oy или Oz соответственно. Для сложного трансформации объекта можно использовать последовательные преобразования, которые выражаются в перемножении (конкатенации) соответствующих матриц элементарных преобразований. Таким образом, можно сначала рассчитать единую (общую) матрицу преобразования (перемножить между собой все элементарные матрицы трансформации), а затем использовать только ее. Так, например, вращение объекта вокруг совей оси и одновременное движение по кругу определенного радиуса, может быть описано следующей последовательностью матриц: Vi *MatRot1(…)*MatTrans(…)*MatRot2(…), где Vi – координаты вершин объекта, MatRot1 – матрица поворота вокруг совей оси, MatTrans – матрица перемещения, MatRot2 – матрица вращения по кругу.

    Вам не придется самостоятельно запоминать и вычислять все матрицы преобразования "вручную", т.к. во всех библиотеках 3D графики они предусмотрены. В библиотеке Direct3D определен матричный тип D3DXMATRIX – это структура, которая содержит 4х4 элементов.

    Элемент матрицы с индексами ij указывает на значение, хранящееся в строке с номером i и столбце с номером j. Например, элемент _32 указывает на значение m[3][2]. Обращаться к элементам матрицы (читать, записывать) можно двумя способами.

    C++

    D3DXMATRIX m; m._11 = …; или m.m[1][1] = …;
    Pascal

    var m: TD3DXMatrix; … m._11 := …; или m.m[1,1] = …;
    Библиотека Direct3D содержит богатый набор процедур и функций по работе с матрицами. Приведем основные функции для работы с матрицами.

    C++

    D3DXMATRIX m;

    // Создание единичной матрицы D3DXMatrixIdentity(&m);

    // Создание матрицы перемещения D3DXMatrixTranslation(&m, dx, dy, dz);

    // Создание матрицы масштабирования D3DXMatrixScaling(&m, kx, ky, kz);

    // Создание матриц вращения D3DXMatrixRotationX(&m, angleX); D3DXMatrixRotationY(&m, angleY); D3DXMatrixRotationZ(&m, angleZ);
    Pascal

    var m: TD3DXMatrix;

    // Создание единичной матрицы D3DXMatrixIdentity(m);

    // Создание матрицы перемещения D3DXMatrixTranslation(m, dx, dy, dz);

    // Создание матрицы масштабирования D3DXMatrixScaling(m, kx, ky, kz);

    // Создание матриц вращения D3DXMatrixRotationX(m, angleX); D3DXMatrixRotationY(m, angleY); D3DXMatrixRotationZ(m, angleZ);

    <


    При работе с матрицами следует учитывать, что преобразования масштабирования и вращения производятся относительно начала координат. Для комбинирования (умножения) двух матриц существует функция D3DXMatrixMultiply(), которая помещает результат перемножения двух матриц, которые передаются в качестве второго и третьего аргументов, в первый. К примеру, мы хотим повернуть объект вокруг оси Oy на угол 30 градусов и переместить его на вектор (1,2,3). Для этого можно воспользоваться правилом композиции двух элементарных преобразований с помощью матрицы поворота и матрицы перемещения.

    C++

    D3DXMATRIX matRotY, matTrans, matRes; D3DXMatrixRotationY( &matRotY, 30*D3DX_PI/180 ); D3DXMatrixTranslation( &matTrans, 1, 2, 3 ); D3DXMatrixMultiply(&matRes, &matRotY, &matTrans);
    Pascal

    var matRotY, matTrans, matRes: TD3XDMatrix;

    D3DXMatrixRotationY( matRotY, 30*pi/180 ); D3DXMatrixTranslation( matTrans, 1, 2, 3 ); D3DXMatrixMultiply( matRes, matRotY, matTrans );
    Если поменять местами матрицы matRotY и matTrans в функции D3DXMatrixMultiply, то результат преобразования будет иной (сначала объект будет перемещен, а затем повернут). Вообще функция D3DXMatrixMultiply сама возвращает результат перемножения двух матриц, и поэтому ее можно использовать как параметр в матричных операциях. Ниже приведен пример такой возможности.

    C++

    D3DXMATRIX matRotY, matTrans; D3DXMatrixRotationY( &matRotY, … ); D3DXMatrixTranslation( &matTrans, … ); device->SetTransform(D3DTS_VIEW, D3DXMatrixMultiply(NULL, &matRotY, &matTrans));
    Pascal

    var matRotY, matTrans, matRes: TD3XDMatrix;

    D3DXMatrixRotationY( matRotY, … ); D3DXMatrixTranslation( matTrans, … ); device.SetTransform(D3DTS_VIEW, D3DXMatrixMultiply(nil, matRotY, matTrans)^);
    Для визуализации некоторого объекта (получение его проекции) нам необходимы следующие данные:

  • Моделируемые объекты трехмерного мира (сцены);
  • Положение виртуальной камеры, которая определяет перспективу.


  • В терминах систем координат процесс получения проекции может быть описан следующей упрощенной блок схемой:


    Принципы построения трехмерной сцены

    Как известно из курса линейной алгебры, переход от одной системы координат к другой эквивалентен смене базиса. Когда нужно преобразовать объект из одного базиса в другой, нужно умножить все вершины объекта на соответствующую матрицу приведения базиса.

    Библиотека Direct3D позволяет работать как в левосторонней системе координат, так и в правосторонней. Далее будем рассматривать все примеры для левосторонней системы координат.

    Локальная система координат (локальное пространство) определяет исходные координаты объекта, т.е. в тех в которых он задан. Моделирование объекта в локальной (собственной) системе координат удобнее, чем напрямую в мировой системе координат. Локальная система позволяет описывать объект, не обращая внимания на положение, размер, ориентацию других объектов в мировой системе координат.

    После того как заданы все объекты в своих собственных (локальных) системах координат, необходимо привязать их к общей мировой системе координат. Процесс трансформации координат объектов, заданных в локальных системах в мировую (общую) называют мировым преобразованием (world transform). Обычно используют 3 типа преобразований: перемещение, масштабирование, вращение. Мировое преобразование описывается с помощью матрицы, используя метод SetTransform интерфейса IDirect3DDevice9. Для применения мирового преобразования метод SetTransform вызывается с параметром D3DTS_WORLD. Предположим, что у нас в сцене присутствуют два объекта: куб и сфера. Мы собираемся отобразить куб в точке с координатами (-3, 2, 6), а сферу в точке (5, 0, -2) мировой системы координат. Это можно проделать с помощью следующих шагов.

    C++

    D3DXMATRIX matCube, matSphere;

    D3DXMatrixTranslation(&matCube, -3.0f, 2.0f, 6.0f); pDirect3DDevice->SetTransform(D3DTS_WORLD, &matCube); drawCube();

    D3DXMatrixTranslation(&matSphere, 5.0f, 0.0f, -2.0f); pDirect3DDevice->SetTransform(D3DTS_WORLD, &matSphere); drawShepre();
    Pascal

    var matCube, matSphere: TD3DXMatrix; … D3DXMatrixTranslation(matCube, -3.0, 2.0, 6.0); device.SetTransform(D3DTS_WORLD, matCube); drawCube;

    D3DXMatrixTranslation(matShpere, 5.0, 0.0, -2.0); device.SetTransform(D3DTS_WORLD, matSphere); drawShepre;
    <


    Построение проекции весьма сложная и очень неэффективная операция когда камера имеет произвольное положение и направление в мировой системе координат. Чтобы упростить эту ситуацию, мы перемещаем камеру в начало мировой системы координат и поворачиваем ее так, чтобы "взгляд" камеры был направлен в положительную сторону оси Z, как показано на рисунке ниже.

    Принципы построения трехмерной сцены

    Матрица перехода в систему координат камеры может быть получена с помощью функции D3DXMatrixLookAtLH(), прототип которой показан ниже:

    D3DXMatrixLookAtLH ( matView, // результат Eye, // положение камеры в мировой системе координат At, // точка в мировой системе, куда направлена камера (взгляд) Up // вектор, указывающий "где верх" в мировой системе координат ).

    Для установки матрицы вида (преобразование в пространство камеры) используется метод SetTransform с первым параметром D3DTS_VIEW.

    Например, поместить камеру в точку (0,0,-3) и направить "взгляд" наблюдателя в начало системы координат, можно с помощью такого кода:

    C++

    D3DXMATRIX matView; D3DXVECTOR3 positionCamera, targetPoint, worldUp;

    positionCamera = D3DXVECTOR3(0,0,-3); targetPoint = D3DXVECTOR3(0,0,0); worldUp = D3DXVECTOR3(0,1,0);

    D3DXMatrixLookAtLH(&matView, &positionCamera, &targetPoint, &worldUp); device->SetTransform(D3DTS_VIEW, &matView);
    Pascal

    var matView: TD3DMatrix; positionCamera, targetPoint, worldUp : TD3DXVector3; … positionCamera := D3DXVector3(0, 0, -3); targetPoint := D3DXVector3(0, 0, 0); worldUp := D3DXVector3(0, 1, 0);

    D3DXMatrixLookAtLH(matView, positionCamera, targetPoint, worldUp); device.SetTransform(D3DTS_VIEW, matView);
    Таким образом, все объекты сцены будут теперь описаны в системе координат камеры (наблюдателя). Если положение и параметры камеры не меняются в течение работы приложения, то вызов функции SetTransform с параметром D3DTS_VIEW производится один раз.

    Принципы построения трехмерной сцены

    Последним этапом преобразования при получении 2D изображения является операция проекции. Библиотека поддерживает работу как с перспективной, так и с ортогональной проекциями.


    Для работы с первым типом проекций предназначена функция D3DXMatrixPerspectiveFovLH(), для ортогональных же проекций нужна функция D3DXMatrixOrthoOffCenterLH(). Мы рассмотрим примеры работы с перспективной проекцией. Матрица перспективной проекции задает положение передней и задней отсекающих плоскостей, искажения, имитирующие перспективу. Прототип функции D3DXMatrixPerspectiveFovLH() приведен ниже.

    D3DXMatrixPerspectiveFovLH( matProj, // результат fov, // вертикальный угол обзора aspect, // отношение ширины окна к высоте zn, // расстояние до передней отсекающей плоскости zf // расстояние до задней отсекающей плоскости )

    Ниже приведены иллюстрации, которые поясняют значение последних четырех аргументов данной функции.

    Принципы построения трехмерной сцены

    Для установки перспективной матрицы необходимо вызвать метод SetTransform с первым параметром D3DTS_PROJECTION. Приведенный ниже пример, создает и устанавливает матрицу проекции со следующими параметрами: вертикальный угол обзора – 45 градусов, расстояние до передней и задней отсекающих плоскостей – 1 и 100 единиц соответственно.

    C++

    D3DXMATRIX matProj;

    D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/2, width/height, 1, 100); device->SetTransform(D3DTS_PROJECTION, &matProj);
    Pascal

    var matProj: TD3DXMatrix;

    D3DXMatrixPerspectiveFovLH(matProj, pi/2, Width/Height, 1, 100); device.SetTransform(D3DTS_PROJECTION, matProj);
    Если параметры проецирования не меняются в течение работы приложения, то вызов функции SetTransform с параметром D3DTS_PROJECTION выполняется единожды.

    Таким образом, конвейер преобразований вершин объекта может быть описан следующей блок-схемой:

    Принципы построения трехмерной сцены

    Каждая вершина трехмерной сцены подвергается следующему преобразованию V' = V*matWorld*matView*matProj, где V – исходная вершина, V' - преобразованная вершина.

    Схема графического конвейера

    В данном разделе мы рассмотрим принципы построения трехмерных сцен с помощью графической библиотеки Direct3D. Вначале необходимо познакомиться со схемой работы графического конвейера. Графический конвейер представляет собой некое аппаратно-программное устройство, которое переводит объекты, описанные в трехмерном пространстве XYZ, с учетом положения наблюдателя, во множество пикселей на экране вашего монитора. Ниже приведена блок-схема работы графического конвейера.
    Схема графического конвейера

    "Жизнь" вершин начинается, когда они попадают в графический конвейер из приложения. На первом шаге графического конвейера все вершины объектов подвергаются аффинным преобразованиям - вращению, масштабированию и перемещению. За этот шаг отвечает блок трансформации и освещения либо же вершинный шейдер. По сути, выполняют они одну и ту же работу по преобразованию вершин, но блок трансформации и освещения жестко задает правила обработки, в то время как вершинный шейдер является программируемым элементом на данной стадии. Здесь же происходит расчет освещенности в вершинах в зависимости от количества, места положения и типа источников света. Вершинный шейдер получает на вход одну вершину, содержащую координаты в локальной системе координат, и выдает ее же, но уже трансформированную в координатах системы наблюдателя (камеры).
    Отсечение невидимых граней "удаляет" все треугольники сцены, которые расположены нелицевыми сторонами к камере (наблюдателю). Программист может указать порядок обхода вершин треугольника (по часовой/против часовой стрелки), при котором библиотека будет блокировать вывод этих примитивов.
    Пользователь может установить дополнительные плоскости отсечения, которые будут отсекать те вершины и треугольники, которые находятся в отрицательном полупространстве плоскостей.
    Отсечение по видимому объему задает пирамиду видимости, и примитивы, которые находятся вне этой пирамиды, будут отсекаться.
    На следующем шаге графического конвейера производится так называемое однородное преобразование, при котором x,y,z координаты вершин делятся на четвертую компоненту, называемую однородным множителем.
    После этого преобразования координаты треугольников будут нормализованы, а объем (пирамида) видимости трансформируется в единичный ортогональный куб.

    Теперь нормализованные координаты отображаются в пространство экрана. На этом шаге "жизнь" вершин в графическом конвейере заканчивается.

    Следующий этап начинается с растеризации треугольников и получении из них массива пикселей. Причем каждый растеризуемый пиксель обладает теми же атрибутами (цвет, текстурные координаты и т.д.) что и вершина после обработки вершинным шейдером. Значения же атрибутов пикселя вычисляются на основе линейной интерполяции вершин примитива.

    Шаг мультитекстурирования может производиться либо с помощью механизма работы с текстурами, либо же с помощью пиксельных шейдеров. Пиксельные шейдеры можно рассматривать как некий программируемый механизм обработки пикселей. Причем способ и результат такой обработки зависит только от программиста. Именно с помощью пиксельных шейдеров получают визуальные эффекты, сравнимые с киноэффектами (моделирование воды, травяной поверхности, меха, микрорельефное и попиксельное освещение и др.).

    После блока мультитекстурирования наступает фаза различных тестов, после успешного прохождения которых, пиксель попадает в буфер кадра (место в видеопамяти) и отображается на экране либо в заменяющую его текстуру.

    Вывод трехмерных объектов

    В первую очередь при визуализации трехмерных объектов, необходимо изменить формат вершины и набор FVF флагов.
    C++
    struct VERTEX3D { FLOAT x, y, z; … }; VERTEX3D data[];
    #define MY_FVF (D3DFVF_XYZ | …);
    Pascal
    type Vertex3D = packed record x, y, z: Single; … end;
    const MY_FVF = D3DFVF_XYZ or …;
    var data: array of Vertex3D;

    Единственное отличие от двумерного случая будет заключаться в наличии операций с матрицами для преобразования объектов. Функции для визуализации трехмерных объектов будут такими же, какие мы использовали в двумерном случае: DrawPrimitive, DrawPrimitiveUP, DrawIndexedPrimitive, DrawIndexedPrimitiveUP. В качестве примера разберем построение, вращающего вокруг одной из координатных осей, цветного треугольника. Запишем шаги, которые мы должны проделать.
  • Определение формата вершин треугольника и набор FVF флагов;
  • Заполнение массива вершин данными;
  • Установка видовой и проекционной матриц;
  • Установка мировой матрицы (поворот вокруг оси) и вывод примитива.

  • Приведем программные строки для каждого шага алгоритма.
    C++
    struct VERTEX3D { FLOAT x, y, z; DWORD color; }; #define MY_FVF (D3DFVF_XYZ | D3DFVF_DIFFUSE); ... VERTEX3D points[3] = { { -1.0f,-1.0f, 0.0f, 0x00ff0000, }, { 0.0f, 1.0f, 0.0f, 0x0000ff00, }, { 1.0f,-1.0f, 0.0f, 0x000000ff, } } ... D3DXMATRIX matWorld, matView, matProj;
    D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3 ( 0.0f, 0.0f,-5.0f ), &D3DXVECTOR3 ( 0.0f, 0.0f, 0.0f ), &D3DXVECTOR3 ( 0.0f, 1.0f, 0.0f ) ); device->SetTransform( D3DTS_VIEW, &matView );
    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f ); device->SetTransform( D3DTS_PROJECTION, &matProj ); ... D3DXMatrixRotationY( &matWorld, angle ); device->SetTransform( D3DTS_WORLD, &matWorld );
    device->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );
    Pascal
    type Vertex3D = packed record x, y, z: Single; color: DWORD; end;
    const MY_FVF = D3DFVF_XYZ or D3DFVF_DIFFUSE;
    var points: array [0..2] of Vertex3D = ( (x: -1; y: -1; z: 0; color: $000000ff), (x: 0; y: 1; z: 0; color: $0000ff00), (x: 1; y: -1; z: 0; color: $00ff0000) );
    matWorld, matView, matProj: TD3DMatrix; ...
    D3DXMatrixLookAtLH(matView, D3DXVector3(0,0,-5), D3DXVector3(0,0,0), D3DXVector3(0,1,0)); device.SetTransform(D3DTS_VIEW, matView);
    D3DXMatrixPerspectiveFovLH(matProj, PI/4, 1, 1, 100); device.SetTransform(D3DTS_PROJECTION, matProj); ... D3DXMatrixRotationY(matWorld, angle); device.SetTransform(D3DTS_WORLD, matWorld); device.DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
    <
    Ниже приведен пример нескольких кадров из анимации.
    Вывод трехмерных объектов

    Библиотека Direct3D располагает механизмом отсечения граней. Идея заключается в том, что можно указать порядок обхода вершин треугольника (по часовой или против часовой стрелки) и отключить вывод тех граней, которые перечислены, например, по часовой стрелке. По умолчанию механизм отсечения граней отключен, т.е. воспроизводятся обе стороны примитива. Программно отключение "задних" сторон примитива реализуется через вывоз метода SetRenderState(D3DRS_CULLMODE, <значение>) интерфейса IDirect3DDevice9. В качестве второго параметра может выступать одна из трех констант: D3DCULL_NONE – механизм отбраковки граней выключен;
    D3DCULL_CW – отбраковываются грани, вершины которых перечислены по часовой стрелке;
    D3DCULL_CCW – отбраковываются грани, вершины которых перечислены против часовой стрелки.
    Так, на представленном ниже рисунке грань (треугольник) задается перечислением своих вершин: v0, v1, v2 – по часовой стрелке; v0, v2, v1 – против часовой стрелки. Вывод трехмерных объектов

    Освещенность и материалы

    Для построения реалистичного изображения недостаточно только удалить невидимые грани объекта. После того как скрытые поверхности удалены, все видимые грани объекта необходимо закрасить, учитывая источники света, характеристики поверхностей, а также взаимное расположение и ориентацию поверхностей и источников света. В компьютерной графике выделяют, как правило, три типа источников света: точечный, параллельный и прожекторный. Более сложным с точки зрения моделирования, но зато более реалистичным источником света является точечный, при использовании которого освещенность поверхности зависит от ее ориентации: если лучи направлены перпендикулярно к поверхности, то она освещена максимально ярко, если же под углом, то освещенность ее убывает. Чем меньше угол падения лучей, тем меньше освещенность. Световая энергия, падающая на поверхность объекта, может быть поглощена, отражена или пропущена. Мы видим объект только благодаря тому, что он отражает или пропускает свет; если же объект поглощает весь падающий свет, то он невидим и называется абсолютно черным телом.
    При расчете освещенности грани в компьютерной графике учитывают следующие типы отражения света от поверхности: рассеянное, диффузное, зеркальное. Интенсивность освещения граней объектов рассеянным светом считается постоянной в любой точке пространства. Это обусловлено тем, что рассеянный свет создается многочисленными отражениями от различных поверхностей пространства. Такой свет практически всегда присутствует в реальной обстановке. Даже если объект защищен от прямых лучей, исходящих от точечного источника света, он все равно будет виден из-за наличия рассеянного света. Интенсивность рассеянного света выражается как Освещенность и материалы, где Освещенность и материалы - интенсивность рассеянного света, Освещенность и материалы - коэффициент рассеянного отражения, зависящий от отражательных свойств материала (поверхности). При освещении объекта только рассеянным светом, все его грани будут закрашены одинаково, а общие ребра будут неразличимы.
    При наличии в сцене точечного источника света, интенсивность диффузного отражения пропорциональна косинусу угла между нормалью к поверхности и направлением на источник света.
    В этом случае для вычисления интенсивности диффузного отражения применяют закон косинусов Ламберта: Освещенность и материалы, где Освещенность и материалы - интенсивность точечного источника света, Освещенность и материалы - коэффициент диффузного отражения, ? - угол падения, рассчитываемый как угол между направлением на источник света и нормалью к поверхности. Если вектора N, L нормализованы, т.е. |N|=|L|=1, то Cos ?=(NL) - скалярное произведение векторов, и уравнение можно записать в виде Освещенность и материалы

    Освещенность и материалы

    Свойством диффузного отражения является равномерность по всем направлениям отраженного света. Поэтому такие объекты имеют одинаковую яркость, вне зависимости от угла обзора. К примеру, матовые поверхности обладают таким свойством. Предположим, что у нас имеется два полигона, одинаково ориентированы относительно источника света, но расположенные на разных расстояниях от него. Если вычислить значения интенсивностей освещенности для каждого полигона, по приведенной формуле, то они окажутся одинаковыми. Это означает, что объекты, расположенные на разном расстоянии от источника света, будут освещаться независимо от их местоположения в трехмерной сцене. В этом случае интенсивность света должна быть обратнопропорциональна квадрату расстояния от источника до грани объекта. Тогда объект, расположенный дальше от источника будет темнее. Тем не менее на практике, как правило, используют не квадрат расстояния, а линейное затухание. В этом случае формула общей интенсивности может быть записана как: Освещенность и материалы где d - расстояние от центра проекции (наблюдателя) до поверхности, k - некоторая константа, kОсвещенность и материалы1.

    Зеркальное отражение можно получить от любой блестящей поверхности. Осветите ярким светом обычное яблоко, и световой блик возникнет в результате зеркального отражения, а свет, отраженный от остальной части яблока, появится вследствие диффузного отражения. Следует отметить, что в том месте, где находится световой блик, яблоко будет казаться не красным, а, скорее всего – белым, т.е. окрашенным в цвет падающего света. Если изменить положение наблюдателя, то световой блик тоже сместится.


    Это объясняется тем, что блестящие поверхности отражают свет неодинаково по всем направлениям. От идеально зеркальной поверхности свет отражается только в том направлении, для которого углы падения и отражения совпадают. Зеркально отраженный свет можно будет увидеть, если угол между вектором отражения (на рисунке обозначен R) и вектором наблюдения (на рисунке обозначен S) равен нулю.

    Освещенность и материалы

    Для неидеальных отражающих поверхностей интенсивность отраженного света резко падает с ростом Освещенность и материалы. В модели, предложенной Фонгом, быстрое убывание интенсивности света, описывается функцией Освещенность и материалы, где Освещенность и материалы в зависимости от вида поверхности. Для идеальной отражающей поверхности Освещенность и материалы. Интенсивность зеркального отражения света по модели Фонга записывается как Освещенность и материалы, где Освещенность и материалы - интенсивность источника света, Освещенность и материалы - коэффициент зеркального отражения. Если вектора R и S нормированы, то формула может быть преобразована к виду: Освещенность и материалы Интенсивность зеркального света обратно пропорциональна расстоянию от источника до грани. Для практических задач здесь используют также модель линейного затухания света, которая выражается следующей формулой: Освещенность и материалы где d - расстояние от наблюдателя до поверхности, k - некоторая константа.

    Таким образом, интенсивность грани складывается из трех составляющих: рассеянного света, диффузного света и зеркального света. Формула расчета общей интенсивности с учетом расстояния от наблюдателя до освещенной грани и трех составляющих света записывается как Освещенность и материалы

    Для более реалистичного отображения объектов сцены в библиотеке Direct3D определены понятия материал и свет. Материал в Direct3D задает свойства поверхности отображаемых примитивов. С помощью материала определяется как будет отражаться от поверхности примитива свет. Материал и свет (в контексте Direct3D) используются совместно. Одно без другого работать не будет.

    Для работы с материалом предусмотрен специальный тип данных D3DMATERIAL9, который содержит следующие поля.

    C++

    typedef struct _D3DMATERIAL9 { D3DCOLORVALUE Diffuse; {рассеянный свет, исходящий от материала} D3DCOLORVALUE Ambient; {определяет окружающий свет} D3DCOLORVALUE Specular; {определяет отражающий (зеркальный) свет} D3DCOLORVALUE Emissive; {определяет излучающий свет материала} float Power; {мощность отражения} } D3DMATERIAL9;
    Pascal

    TD3DMaterial9 = packed record Diffuse: TD3DColorValue; {рассеянный свет, исходящий от материала} Ambient: TD3DColorValue; {определяет окружающий свет} Specular: TD3DColorValue; {определяет отражающий (зеркальный) свет} Emissive: TD3DColorValue; {определяет излучающий свет материала} Power: Single; {мощность отражения} end;
    <


    Задавая параметры структуры D3DMATERIAL9, тем самым мы определяем свойства поверхности отображаемого объекта. Процесс создания и использование (назначение) материала включает в себя следующие шаги:

  • Объявление переменной типа D3DMATERIAL9;
  • Заполнение соответствующих полей данной структуры;
  • Установка материала.


  • Программно эти шаги реализуются таким образом:

    C++

    D3DMATERIAL9 material; ZeroMemory( &material, sizeof(D3DMATERIAL9) ); material.Diffuse=D3DXCOLOR(0.7f, 0.0f, 0.0f, 0.0f); material.Ambient=D3DXCOLOR(0.2f, 0.0f, 0.0f, 0.0f); material.Specular=D3DXCOLOR(0.1f, 0.0f, 0.0f, 0.0f); ... device->SetMaterial( &material );
    Pascal

    var material: TD3DMaterial9; ... ZeroMemory(@material,SizeOf(TD3DMaterial9)); material.Diffuse:=D3DXColor(0.7, 0, 0, 0); // r, g, b, a material.Ambient:=D3DXColor(0.2, 0, 0, 0); material.Specular:=D3DXColor(0.1, 0, 0, 0); ... device.SetMaterial(material);
    Метод SetMaterial вызывается при выводе объектов (в процедуре рендеринга). Кроме определения свойств материала необходимо задать источники света. Библиотека Direct3D поддерживает три типа источника света:

  • Параллельный (направленный) – этот тип не имеет определенного источника света, он как бы повсюду, но светит в одном направлении.

    Освещенность и материалы

  • Точечный – источник, светящий во всех направлениях (лампочка).

    Освещенность и материалы

  • Прожекторный (нацеленный) – тип, имеющий определенный источник света, но светящий в заданном направлении в виде направленного конуса (фонарик).

    Освещенность и материалы



  • Свет в Direct3D состоит из трех составляющих: рассеянного, окружающего, зеркального, которые независимо друг от друга принимают участие в вычислении освещения граней. Они напрямую взаимодействуют с тремя цветами, определенными в свойствах материала. Результат такого сочетания (взаимодействия) и есть конечный цвет освещения сцены. В сценах Direct3D могут присутствовать до восьми различных по свойствам источников света. Для инициализации источника света в Direct3D необходимо проделать следующие шаги:

  • Объявить переменную типа D3DLIGHT9;
  • Указать тип источника и заполнить необходимые поля данной структуры;
  • Включить (разрешить) освещенность в сцене;
  • Установить источники света;
  • Включить источники света.



  • Структура D3DLIGHT9 содержит следующие поля:

    C++

    typedef struct _D3DLIGHT9 { D3DLIGHTTYPE Type; { тип источника света} D3DCOLORVALUE Diffuse; {рассеянный свет, излучающий источником} D3DCOLORVALUE Specular; {зеркальный свет, излучающий источником} D3DCOLORVALUE Ambient; {окружающий свет, излучающий источником} D3DVECTOR Position; {положение источника в сцене} для точечного D3DVECTOR Direction; {направление падающего света (вектор)} float Range; {максимальное расстояние освещения} для точечного float Falloff; {используется в прожекторном источнике} float Attenuation0; {определяют закон изменения освещения} float Attenuation1; {от параметра расстояния} float Attenuation2; {1/(A0+A1*D+A2*D^2), D–расстояние от источника} float Theta; {внутренний угол источника света} float Phi; {внешний угол, [0…pi]} } D3DLIGHT9;
    Pascal

    TD3DLight9 = packed record _Type: TD3DLightType; {тип источника света} Diffuse: TD3DColorValue; {рассеянный свет, излучающий источником} Specular: TD3DColorValue; {зеркальный свет, излучающий источником} Ambient: TD3DColorValue; {окружающий свет, излучающий источником} Position: TD3DVector; {положение источника в сцене} для точечного Direction: TD3DVector; {направление падающего света (вектор)} Range: Single; {максимальное расстояние освещения} для точечного Falloff: Single; {используется в прожекторном источнике} Attenuation0: Single; {определяют закон изменения освещения} Attenuation1: Single; {от параметра расстояния} Attenuation2: Single; {1/(A0+A1*D+A2*D^2), D–расстояние от источника} Theta: Single; {внутренний угол источника света} Phi: Single; {внешний угол, [0…pi]} end;
    Тип источника задается указанием одной из трех констант:

    D3DLIGHT_POINT, // точечный источник D3DLIGHT_SPOT, // источник-прожектор D3DLIGHT_DIRECTIONAL. // направленный источник

    Ниже приведен пример установки направленного источника освещения в сцене.

    C++

    // создание и заполнение полей структуры, установка типа источника D3DLIGHT9 light; ZeroMemory(&light, sizeof(D3DLIGHT9) ); light.Type = D3DLIGHT_DIRECTIONAL; light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f); light.Direction = D3DXVECTOR3(0.0f, 0.0f,1.0f); ... // разрешаем работы с освещенностью device->SetRenderState( D3DRS_LIGHTING, TRUE ); ... // установка и включение первого (и единственного) источника света device->SetLight( 0, &light ); device->LightEnable( 0, TRUE );
    Pascal

    var light: TD3DLight9; ... // создание и заполнение полей структуры, установка типа источника ZeroMemory(@light,SizeOf(TD3DLight9)); light._Type:=D3DLIGHT_DIRECTIONAL; light.Diffuse:=D3DXColor(1,0,0,0); light.Direction:=D3DXVector3(0,0,1);

    // разрешаем работы с освещенностью device.SetRenderState(D3DRS_LIGHTING, 1); ...

    // установка и включение первого (и единственного) источника света device.SetLight(0,light); device.LightEnable(0,true);

    <


    Но одних материалов и источников света недостаточно для того, чтобы объекты сцены были освещены. При использовании освещения необходимо определить нормаль к каждой грани трехмерного примитива. Нормаль представляет собой вектор, расположенный перпендикулярно одной их сторон выводимого примитива.

    Освещенность и материалы

    Именно с помощью нормалей рассчитывается освещенность объекта (граней). Как правило, нормали задаются в каждой вершине примитива.

    Освещенность и материалы

    Поэтому необходимо корректно изменить формат вершин и набор FVF флагов.

    C++

    struct VERTEX3D { FLOAT x, y, z; FLOAT nx, ny, nz; }; VERTEX3D data[];

    #define MY_FVF (D3DFVF_XYZ | D3DFVF_NORMAL);
    Pascal

    type Vertex3D = packed record x, y, z: Single; nx, ny, nz: Single; end;

    const MY_FVF = D3DFVF_XYZ or D3DFVF_NORMAL;

    var data: array of Vertex3D;
    Следует заметить, что для правильной освещенности граней объектов все вектора, участвующие в расчете освещенности, должны быть нормированы (длина вектора равна единице). Библиотека Direct3D располагает функцией D3DXVec3Normalize(), которая позволяет привести вектор к единичной длине.

    C++

    D3DXVECTOR3 direction = D3DXVECTOR3(0.0f, 0.0f,1.0f); D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &direction );
    Pascal

    D3DXVec3Normalize( light.Direction, D3DXVector3(0, 0, 1) );
    Кроме того, чтобы все нормали вершин после серии преобразований автоматически имели длину единица, можно установить переменную состояния D3DRS_NORMALIZENORMALS в значение "истина".

    C++device->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE );
    Pascaldevice.SetRenderState(D3DRS_NORMALIZENORMALS, 1 );
    Для того, чтобы источник в сцене был активирован нужно проделать следующие шаги. Сначала нужно вызвать процедуру установки (регистрации) нужного источника света. Это реализуется через вызов метода SetLight() интерфейса IDirect3DDevice9. Данный метод имеет два параметра: номер источника (лампы), и второй – указатель на переменную типа D3DLIGHT9. Второй шаг состоит в вызове метода LightEnable() интерфейса IDirect3DDevice9 для включения/выключения нужного источника света.


    Данный метод имеет два параметра: номер источника и второй – булевская переменная (истина- включение источника, ложь - выключение).

    Ниже приведен пример освещения направленным источником света треугольной грани, у которой нормали в каждой вершине одинаковы. При этом положение камеры (наблюдателя) задано точкой (2,2,-2), а направление лучей света – (-2,-2,2).

    Освещенность и материалы

    Как видно в каждом положении грань освещена одинаково (нормали в каждой вершине равны (0, 0, -1)). Изменив хотя бы одну нормаль вершины, можно добиться того, что грань будет освещаться уже неравномерно. Пусть одна из вершин будет теперь иметь нормаль (1,0,-1). Результат освещенности грани при таких изменениях нормали показан ниже.

    Освещенность и материалы

    Ниже приведены примеры заполнения "нужных" полей для точечного и прожекторного источников света.

    Пример для точечного источника света.

    C++

    D3DLIGHT9 light; ZeroMemory( &light, sizeof(D3DLIGHT9) ); light.Type = D3DLIGHT_POINT; light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f); light.Position = D3DXVECTOR3(2.0f, 2.0f, -2.0f); light.Attenuation0 = 1.0f; light.Range = 100;
    Pascal

    var light: TD3DLight9; ... ZeroMemory(@light, SizeOf(TD3DLight9)); light._Type:=D3DLIGHT_POINT; light.Diffuse:=D3DXColor(1,1,0,0); light.Position:=D3DXVector3(2,2,-2); light.Attenuation0:=1; light.Range:=100;
    Пример для прожекторного источника света.

    C++

    D3DLIGHT9 light; ZeroMemory( &light, sizeof(D3DLIGHT9) ); light.Type = D3DLIGHT_SPOT; light.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 0.0f); light.Position = D3DXVECTOR3(2.0f, 2.0f, -2.0f); D3DXVECTOR3 direction = D3DXVECTOR3(-2.0f, -2.0f, 2.0f); D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &direction ); light.Attenuation0 = 1.0f; light.Range = 100; light.Phi = D3DX_PI/2; light.Theta = D3DX_PI /3; light.Falloff = 1.0f;
    Pascal

    var light: TD3DLight9; ... ZeroMemory(@light, SizeOf(TD3DLight9)); light._Type:=D3DLIGHT_SPOT; light.Diffuse:=D3DXColor(1,1,0,0); light.Position:=D3DXVector3(2,2,-2); D3DXVec3Normalize(light.Direction, D3DXVector3(-2,-2,2)); light.Attenuation0:=1; light.Range:=100; light.Phi:=pi/2; light.Theta:=pi/3; light.Falloff:=1;

    Построение стандартных объектов

    Библиотека Direct3D располагает рядом встроенных функций для построения простых стандартных трехмерных примитивов (куб, цилиндр, сфера, тор):
  • D3DXCreatePolygon // полигон
  • D3DXCreateBox // параллелограмм
  • D3DXCreateCylinder // цилиндр
  • D3DXCreateSphere // сфера
  • D3DXCreateTorus // тор
  • D3DXCreateTeapot // чайник

  • При создании объектов таким способом вершины "получают" формат, в котором присутствует положение (D3DFVF_XYZ) и нормаль (D3DFVF_NORMAL). Существует возможность задавать уровень детализации при создании перечисленных выше трехмерных примитивов. Следует заметить, что нормаль к каждой вершине созданного объекта вычисляется автоматически. Рассмотрим их функции создания и визуализации. Для работы с подобными примитивами необходимо объявить переменную интерфейсного типа ID3DXMesh, в которой и будет "храниться" трехмерный объект. Визуализация созданного объекта осуществляется вызовом метода DrawSubset интерфейса ID3DXMesh.
    Функция создания полигона (правильного многоугольника):
    D3DXCreatePolygon( ссылка на устройство вывода, длина стороны полигона, количество сторон полигона, результат, указатель на смежные треугольники);
    Ниже приведен пример создания и визуализации трехмерного объекта – полигона.
    C++
    // объявление переменной LPD3DXMESH polygon; // создание объекта полигон D3DXCreatePolygon( device, 0.5, 10, &polygon, NULL); // визуализация polygon->DrawSubset(0);
    Pascal
    // объявление переменной var polygon: ID3DXMesh; // создание объекта полигон D3DXCreatePolygon(device, 0.5, 10, polygon, nil); // визуализация polygon.DrawSubset(0);

    Ниже приведены примеры построения полигонов с разными значениями второго и третьего параметра рассмотренной функции.
    Длина стороны = 0.5
    Количество сторон = 10
    Длина стороны = 2
    Количество сторон = 3
    Длина стороны = 0.1
    Количество сторон = 36

    Построение стандартных объектов

    Построение стандартных объектов

    Построение стандартных объектов

    Функция вывода параллелепипеда
    D3DXCreateBox( ссылка на устройство вывода, ширина, высота, глубина, результат, указатель на смежные треугольники);
    Ниже приведен пример создания и визуализации трехмерного объекта – полигона.

    C++

    // объявление переменной LPD3DXMESH box; // создание объекта D3DXCreateBox( device, 1.0f, 0.5f, 2.0f, &box, NULL); // визуализация box->DrawSubset(0);
    Pascal

    // объявление переменной var box: ID3DXMesh; // создание объекта D3DXCreateBox(device, 1, 0.5, 2, box, nil); // визуализация box.DrawSubset(0);
    Примеры вывода параллелограмма с различными длинами сторон

    ширина = 1

    высота = 0.5

    глубина = 2
    ширина = 1

    высота = 1

    глубина = 1
    ширина = 0.5

    высота = 2

    глубина = 1


    Построение стандартных объектов


    Построение стандартных объектов


    Построение стандартных объектов
    Функция построения цилиндра

    D3DXCreateCylinder( ссылка на устройство радиус первого основания радиус второго основания высота цилиндра количество разбиений "по радиусу" количество разбиений "по длине" результат указатель на смежные треугольники);

    Построение стандартных объектов

    C++

    LPD3DXMESH cylinder; D3DXCreateCylinder(device, 0.2f, 0.2f, 1, 16, 3, &cylinder, NULL);

    cylinder->DrawSubset(0);
    Pascal

    var cylinder: ID3DXMesh; D3DXCreateCylinder(device, 0.2, 0.2, 1, 16, 3, cylinder, nil);

    cylinder.DrawSubset(0);
    Первое основание = 0.2

    Второе основание = 0.2

    Разбиений по радиусу = 16
    Первое основание = 0.4

    Второе основание = 0.2

    Разбиений по радиусу = 16
    Первое основание = 0.4

    Второе основание = 0

    Разбиений по радиусу = 6


    Построение стандартных объектов


    Построение стандартных объектов


    Построение стандартных объектов
    Функция построения сферы:

    D3DXCreateSphere( ссылка на устройство, радиус сферы, разбиений "по радиусу", // число апельсиновых долек разбиений "вдоль", // результат, указатель на смежные треугольники);
    Разбиений по радиусу = 6, вдоль = 32Разбиений по радиусу = 32, вдоль = 6


    Построение стандартных объектов


    Построение стандартных объектов
    C++

    LPD3DXMESH sphere; D3DXCreateSphere(device, 1.0f, 16, 16, &sphere, NULL);

    sphere->DrawSubset(0);
    Pascal

    var sphere: ID3DXMesh; D3DXCreateSphere(device, 1, 16, 16, sphere, nil);

    sphere.DrawSubset(0);
    Функция построения тора:

    D3DXCreateTorus( ссылка на устройство, внутренний радиус тора, внешний радиус тора, количество разбиений в поперечном сечении, количество разбиений по кругу, результат, указатель на смежные треугольники);
    Разбиений в сечение = 6

    Разбиений по кругу = 32
    Разбиений в сечение = 32

    Разбиений по кругу = 6


    Построение стандартных объектов


    Построение стандартных объектов
    <


    table class="xml_table" cellpadding="2" cellspacing="1">C++

    LPD3DXMESH torus; D3DXCreateTorus(device, 0.2f, 0.8f, 32, 6, &torus, NULL); torus->DrawSubset(0); Pascal

    var torus: ID3DXMesh; D3DXCreateTorus(device, 0.2, 0.8, 32, 6, torus, nil);

    torus.DrawSubset(0); Функция построения чайника:

    D3DXCreateTeapot( ссылка на устройство, результат, указатель на смежные треугольники);

    Это единственный примитив, для которого отсутствует возможность задать уровень детализации.

    Построение стандартных объектов

    C++

    LPD3DXMESH teapot; D3DXCreateTeapot(device, &teapot, NULL); teapot->DrawSubset(0);
    Pascal

    var teapot: ID3DXMesh; D3DXCreateTeapot(device, teapot, nil);

    teapot.DrawSubset(0);
    Ниже приведены примеры освещения трехмерного объекта точечным источником света с различными характеристиками.

    light.Ambient = D3DXCOLOR(0.3f, 0,0,0);

    Построение стандартных объектов


    light.Diffuse = D3DXCOLOR(0.5f, 0.0, 0,0); light.Ambient = D3DXCOLOR(0.2f, 0,0,0);


    Построение стандартных объектов
    light.Diffuse = D3DXCOLOR(0.5, 0.0, 0,0); light.Ambient = D3DXCOLOR(0.2, 0,0,0); light.Specular = D3DXCOLOR(0.3,0,0.0,0);

    Построение стандартных объектов
    Следует заметить, что зеркальное освещение требует больших вычислений, чем другие типы освещения, поэтому библиотека Direct3DM предоставляет возможность отключать его (по умолчанию оно выключено). Для включения зеркального освещения необходимо установить константу состояния D3DRS_SPECULARENABLE в значение "истина".

    C++device->SetRenderState( D3DRS_SPECULARENABLE, TRUE );
    Pascaldevice.SetRenderState(D3DRS_SPECULARENABLE, 1);
    Как мы уже говорили, библиотека Direct3D позволяет одновременно использовать (обсчитывать) в сцене до восьми источников света. Программно использование нескольких источников света в сцене накладывает на программиста дополнительные "обязанности" по созданию и заполнению полей структуры для каждого источника. Ниже приведен пример использования трех разноцветных точечных источников света в сцене, которые расположены на координатных осях системы координат.

    C++

    D3DMATERIAL9 material; D3DLIGHT9 light0, light1, light2;

    ZeroMemory( &material, sizeof(D3DMATERIAL9) ); material.Diffuse=D3DXCOLOR(1.0f, 1.0f, 1.0f, 0.0f);

    ZeroMemory( &light0, sizeof(D3DLIGHT9) ); light0.Type = D3DLIGHT_POINT; light0.Diffuse = D3DXCOLOR(0.5f, 0.0f, 0.0f, 0.0f); light0.Position = D3DXVECTOR3(4.0f, 0.0f, 0.0f); … ZeroMemory( &light1, sizeof(D3DLIGHT9) ); light1.Type = D3DLIGHT_POINT; light1.Diffuse = D3DXCOLOR(0.0f, 0.5f, 0.0f, 0.0f); light1.Position = D3DXVECTOR3(0.0f, 0.0f, -4.0f); … ZeroMemory( &light2, sizeof(D3DLIGHT9) ); light2.Type = D3DLIGHT_POINT; light2.Diffuse = D3DXCOLOR(0.0f, 0.0f, 0.5f, 0.0f); light2.Position = D3DXVECTOR3(-4.0f, 0.0f, 0.0f);

    device->SetRenderState( D3DRS_LIGHTING, TRUE ); ... // процедура рендеринга device->SetLight( 0, &light0 ); device->LightEnable( 0, TRUE ); device->SetLight( 1, &light1 ); device->LightEnable( 1, TRUE ); device->SetLight( 2, &light2 ); device->LightEnable( 2, TRUE );
    Pascal

    var material: TD3DMaterial9; light0, light1, light2: TD3DLight9;

    ZeroMemory(@material,SizeOf(TD3DMaterial9)); material.Diffuse:=D3DXColor(1,1,1,0);

    light0._Type:=D3DLIGHT_POINT; light0.Diffuse:=D3DXColor(0.5, 0.0, 0, 0); light0.Position:=D3DXVector3(4,0,0); … light1._Type:=D3DLIGHT_POINT; light1.Diffuse:=D3DXColor(0.0, 0.5, 0, 0); light1.Position:=D3DXVector3(0,0,-4); … light2._Type:=D3DLIGHT_POINT; light2.Diffuse:=D3DXColor(0.0, 0.0, 0.5, 0); light2.Position:=D3DXVector3(-4,0,0); … device.SetRenderState(D3DRS_LIGHTING, 1);

    // процедура рендеринга device.SetLight(0,light0); device.LightEnable(0,true); device.SetLight(1,light1); device.LightEnable(1,true); device.SetLight(2,light2); device.LightEnable(2,true);
    <


    Построение стандартных объектов

    Сложные ( содержащие десятки тысяч вершин) объекты, как правило, создаются в профессиональных трехмерных редакторах (3D Studio, Maya, LightWave, и т.п.). Библиотека Direct3D содержит средства для загрузки и визуализации моделей, содержащихся в x-файлах. Х-файл – файл, содержащий в себе данные о трехмерном объекте (координаты вершин, материалы, нормали, текстуры и анимацию). Вначале необходимо объявить нужные переменные.

    C++LPD3DXMESH mesh;
    Pascalvar mesh: ID3DXMesh;
    Для загрузки трехмерного объекта (модели) из Х-файла предназначена функция

    D3DXLoadMeshFromX( путь до файла на диске, набор флагов, указатель на устройство вывода, информация о смежных треугольниках, данные о материалах модели, данные об эффектах модели, количество материалов в модели, результат ).

    В простейшем случае (если модель не содержит информации о материалах, текстурах, смежности граней) параметры с четвертого по седьмой включительно выставляют в NULL. Ниже приведен пример загрузки модели из Х-файла.

    C++

    D3DXLoadMeshFromX("ship.x", D3DXMESH_MANAGED, device, NULL, NULL, NULL, NULL, &mesh);
    Pascal

    D3DXLoadMeshFromX('ship.x', D3DXMESH_MANAGED, device, nil, nil, nil, nil, mesh);
    Для визуализации трехмерной модели из Х-файла необходимо вызвать метод DrawSubset интерфейса ID3DXMesh, где в качестве параметра передается номер выводимой группы (подмножества) модели. Если модель не разбита на группы, то параметр метода равен нулю.

    C++mesh->DrawSubset(0);
    Pascalmesh.DrawSubset(0);
    Для того чтобы трехмерная модель выглядела более реалистично, можно добавить в сцену источники света и определить материалы.

    Построение стандартных объектов

    Реалистичные построения

    Принцип текстурирования трехмерных объектов ничем не отличается от двумерного аналога. Для каждой треугольной грани необходимо задать текстурные координаты. Единственное отличие от двумерного варианта заключается в формате вершин и наборе FVF флагов.
    C++
    struct VERTEX3D { FLOAT x, y, z; FLOAT u, v; }; #define MY_FVF (D3DFVF_XYZ | D3DFVF_TEX1); ... VERTEX3D points[3] = { { -1.0f,-1.0f, 0.0f, 0.0f, 1.0f, }, { 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, }, { 1.0f,-1.0f, 0.0f, 1.0f, 1.0f, } }
    Pascal
    type Vertex3D = packed record x, y, z: Single; u, v: Single; end;
    const MY_FVF = D3DFVF_XYZ or D3DFVF_TEX1;
    var points: array [0..2] of Vertex3D = ( (x: -1; y: -1; z: 0; u: 0; v: 1), (x: 0; y: 1; z: 0; u: 0.5; v: 0), (x: 1; y: -1; z: 0; u: 1; v: 1) );

    Ниже показан пример вывода куба, у которого все грани покрыты различными текстурами. Для этого можно объявить массив для хранения текстур из шести элементов типа IDirect3DTexture9.
    C++
    LPDIRECT3DTEXTURE9 textures[6]; LPCSTR files[] = { {"bricks.bmp"}, {"colors.bmp"}, {"Lake.bmp"}, {"stone_wall.bmp"}, {"wall.bmp"}, {"fur.jpg"} }; ... for (int i=0; i<6; i++) D3DXCreateTextureFromFile( device, files[i], &textures[i] );
    Pascal
    var textures: array [0..5] of IDirect3DTexture9; files: array [0..5] of AnsiString = ( ('bricks.bmp'), ('colors.bmp'), ('Lake.bmp'), ('stone_wall.bmp'), ('wall.bmp'), ('fur.jpg') ); ... for i:=0 to 5 do D3DXCreateTextureFromFile(device, PChar(files[i]), textures[i]);

    Теперь если куб у нас храниться в виде 12 независимых треугольных граней, то его вывод может быть осуществлен с помощью следующего кода.
    C++
    for (int i=0; i<6; i++) { device->SetTexture(0, &textures[i]); device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2); }
    Pascal
    for i:=0 to 5 do begin device.SetTexture(0, textures[i]); device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2); end;

    Ниже приведены примеры (кадры анимации) вращения такого куба.
    Реалистичные построения

    Рассмотрим один из способов построения полупрозрачных трехмерных объектов.
    Данный метод прозрачности применим только для выпуклых объектов. Идея его заключается в следующем. Вначале выводятся те грани объекта, которые считаются невидимыми для наблюдателя (задние грани). Затем включается режим полупрозрачности (смешивания цветов). Выводим грани объекта, которые видны наблюдателю. Отображение "передних" и "задних" для наблюдателя сторон объекта можно осуществить с помощью механизма отсечения граней. Предположим, что вершины грани перечислены по часовой стрелке. Тогда отключение вывода таких граней отобразит нам "задние" стороны примитива (в нашем случае куба). Программно этот шаг может выглядеть так.

    C++

    device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW ); for (int i=0; i<6; i++) { device->SetTexture(0, &textures[i]); device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2); }
    Pascal

    device.SetRenderState(D3DRS_CULLMODE, D3DCULL_CW); for i:=0 to 5 do begin device.SetTexture(0,textures[i]); device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2); end;
    Ниже показан пример вывода "внутренних" сторон куба.

    Реалистичные построения

    Включение полупрозрачности с нужными параметрами смешивания цветов.

    C++

    device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR ); device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR );
    Pascal

    device.SetRenderState(D3DRS_ALPHABLENDENABLE, 1); device.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR); device.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);
    Переключение механизма отсечения граней на противоположный. Теперь необходимо не выводить грани, которые перечислены против часовой стрелки (задние грани). И вызов функции отображения треугольников.

    C++

    device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); for (int i=0; i<6; i++) { device->SetTexture(0, &textures[i]); device->DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2); }
    Pascal

    device.SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); for i:=0 to 5 do begin device.SetTexture(0,textures[i]); device.DrawPrimitive(D3DPT_TRIANGLELIST, i*6, 2); end;
    Ниже приведен пример вывода полупрозрачного куба, внутри которого размещена трехмерная модель цветка.

    Реалистичные построения

    Для придания трехмерной сцене еще большей реалистичности можно добавить тени, которые отбрасывают объекты при освещении их некоторым источником света. Алгоритмы затенения в случае точечных источников света схожи с алгоритмами удаления невидимых поверхностей, с единственным лишь отличием. В алгоритмах удаления невидимых граней определяются поверхности, которые можно увидеть из точки положения наблюдателя, а в алгоритмах затенения выделяются поверхности, которые можно "увидеть" из местоположения источника света. Грани, видимые как из точки наблюдения, так и из источника света, не лежат в тени. Те же поверхности, которые видимы из точки наблюдения, но не видимы из источника света, находятся в тени.

    Рассмотрим один из простейших и очень быстрых алгоритмов построения теней. Он предназначен для просчета резких теневых участков от полигональных объектов на ровную поверхность (плоскость). Конечно, этот алгоритм очень ограничен в возможностях, но в то же время он прост для реализации. Основная идея метода заключается в том, что для каждого полигона (треугольной грани) сцены, создается еще один полигон, который будет представлять собой тень первого. Все вершины тени будут находиться в пределах плоскости, на которую производится проекция. Алгоритм построения тени будет содержать следующие шаги:

  • Создать луч, начинающийся в источнике света, проходящий через вершину полигона и пересекающий плоскость проецирования;
  • Получить (найти) точку пересечения этого луча с плоскостью;
  • Проделать шаги 1 и 2 для всех вершин грани, получив таким образом координаты "теневого" полигона и затем отрисовать его обычными методами.


  • Пусть у нас источник света находится в точке L=(Lx,Ly,Lz), треугольная грань объекта имеет координаты вершин P=(Px,Py,Pz), Q=(Qx,Qy,Qz), R=(Rx,Ry,Rz), а плоскость проецирования (затенения) Реалистичные построения задается неявным уравнением: Ax+By+Cz+D=0. Требуется определить координаты проекций вершин P,Q,R на плоскость Реалистичные построения. Обозначим через M=(Mx,My,Mz),N=(Nx,Ny,Nz),O=(Ox,Oy,Oz) - проекции точек P,Q,R соответственно на плоскость Реалистичные построения. Найдем координаты точки M. Так как точка M лежит на луче, то будет справедливы следующие уравнения Реалистичные построения для некоторого параметра t.

    С другой стороны точка M лежит в плоскости Реалистичные построения, поэтому верно уравнение: A*Mx+B*My+C*Mz+D=0.

    Подставим выражения для Mx,My,Mz в уравнение плоскости: A*(Lx+t(Px-Lx))+B*(Ly+t(Py-Ly))+C*(Lz+t(Pz-Lz))+D=0. Это линейное уравнение относительно параметра t, решая которое находим, что Реалистичные построения Если Реалистичные построения, то координаты точки пересечения луча и плоскости вычисляются по следующим формулам: Реалистичные построения

    Аналогичные шаги проделываем для двух других вершин полигона Q и R. Следует заметить, что при вычислении тени на плоскость y=0, параметр Реалистичные построения.

    Рассмотренный метод имеет несколько "подводных камней". Тень будет прорисовываться даже в том случае, если источник света находится между полигоном и плоскостью проецирования или же источник света расположен вообще по другую сторону от плоскости. Поэтому такие ситуации необходимо отслеживать и корректно обрабатывать.

    Реалистичные построения

    Ниже приводится пример отбрасывания тени от трехмерного объекта (птицы) на плоскость, покрытую текстурой. В одном случае "тень" является непрозрачной, а в другом – прозрачной.

    Реалистичные построения

    Рассмотрим способ построения ландшафтов и поверхностей средствами библиотеки Direct3D. Данная задача сводится к тому, чтобы на регулярной сетке из треугольников построить трехмерную поверхность (график функции двух переменных, земная поверхность, и т.п.).

    Реалистичные построения

    Разберем построение трехмерной поверхности, представляющей собой функцию двух переменных y=f(x,z). Пусть для простоты у нас область определения функции f(x,z) ограничена единичным квадратом, т.е. 0<=x<=1, 0<=z<=1. Визуализация рассмотренных поверхностей на растровых устройствах может быть осуществлена путем кусочно-линейной аппроксимации треугольными гранями. Такая триангуляционная поверхность может быть получена следующим образом. Параметрическое пространство x,z (единичный квадрат) дискретизируется по координате x с шагом dx, по координате z – с шагом dz. Для каждой пары значений (x,z) вычисляется точка на поверхности y=f(x,z). Полученный таким образом массив точек триангулируется регулярной сеткой.

    Реалистичные построения

    Ниже приведены два примера построения трехмерных поверхностей (график функции двух переменных и элементарная поверхность Безье третьей степени) , причем на обе поверхности наложены текстуры.

    Реалистичные построения

    Реалистичные построения

    Разберем один из способов построения ландшафтов, который базируется на так называемой карте высот. Карта высот представляет собой двумерный массив, где каждый элемент (пиксель) определяет высоту точки в сетке треугольников. Как правило, значение высоты ранжируется в диапазоне от 0 до 255. Поэтому для хранения подобных карт высот используют изображения в оттенках серого цвета (grayscale image). Более светлые участки на таких изображениях соответствуют более высоким значениям точек, а темные участки определяют низкие значения высот. Кроме этого подбирается текстура, схожая по "характеру" с ландшафтом. Ниже приведен пример построения ландшафта по карте высот.

    Реалистичные построения Реалистичные построения

    Графический процессор в задачах обработки изображений

    Проблема большого количества вычислений возникает в ряде задач многих направлений науки и техники. На сегодняшний день, когда с помощью компьютеров решаются, чуть ли не все задачи человечества, применение электронных вычислительных машин является естественно разумным шагом. Среди всего множества задач можно выделить некоторый спектр, для решения которых требуется от нескольких часов до нескольких дней машинного времени. Среди таких ресурсоемких в вычислительном плане задач и направлений можно отметить следующие: предсказание погоды, климата и глобальных изменений в атмосфере, генетика человека, астрономия, транспортные задачи, гидро- и газодинамика, управляемый термоядерный синтез, разведка нефти и газа, вычислительные задачи наук о мировом океане, распознавание изображений и синтез речи. Использование суперкомпьютеров с большим количеством независимо работающих параллельных процессоров и технологий высокопроизводительных вычислений может существенно снизить заявленные выше временные оценки. Такой подход
    является заведомо очень дорогим в финансовом плане и позволителен для узкого круга исследователей и ученых. Тем не менее, сейчас в области настольных персональных компьютеров начинают широко распространяться процессоры с несколькими независимыми ядрами, что позволяет решать уже некоторые задачи в параллельном режиме более широкому кругу обычных пользователей и специалистов. Однако количество ядер в таких процессорах ограничено, как правило, двумя либо четырьмя штуками, что не всегда дает особого приращения производительности. Но современный персональный компьютер в большинстве случаев может быть оснащен сегодня помимо мощного центрального процессора (CPU - Central Processing Unit) еще и современной видеокартой с графическим процессором (GPU - Graphics Processing Unit) производительностью несколько сотен миллиардов операций с плавающей точкой в секунду. Даже самые современные серверные процессоры далеки от такой производительности. Вообще изначально область применения графического пр оцессора была просчет и отображение трехмерных сцен.
    Однако, с появлением видеокарт, позволяющих их программировать, круг вычислительных задач, решаемых с помощью графического процессора, существенно расширился. Поэтому возникает резонный вопрос. Почему бы не задействовать вычислительные ресурсы графического процессора для решения задач отличных от его "традиционных" графических? Как мы уже уяснили, обработка вершин и пикселей в графическом конвейере ведется в параллельном режиме. Количество параллельных блоков по преобразованию вершин зависит от модели графического процессора и может колебаться от 2 до 8 штук. Аналогично блок пиксельной обработки также функционирует в параллельном режиме, причем количество пиксельных блоков может быть от 4 до 48 штук в зависимости от типа видеокарты (на момент написания данных строк).

    В качестве области исследования и проведения вычислительных экспериментов рассмотрим точечные процессы цифровой обработки изображений. С цифровой обработкой изображений сталкиваются при решении многих научных и технических задач. Обработка изображений в широком смысле слова означает выполнение различных операций над многомерными сигналами, которыми изображения и являются. Цели, преследуемые при обработке изображений весьма различны и, как правило, зависят от конкретной решаемой задачи. Это может быть улучшение яркости или контраста в вашей домашней коллекции цифровых фотографий, получение монохромных (бинарных) изображений, обработка изображений сглаживающими фильтрами для удаления шумов и мелких искажений, выделение значимых признаков на изображении с применением в последствии алгоритмов распознавания, например, формы символов. Современная жизнь ставит ряд требований к подобным методам обработки. Одно из самых главных это высокая эффективность и скорость работы алгоритмов с использованием персонального

    ФЄ компьютера. Традиционно обработкой изображений занимался центральный процессор системы. Для этого каждый элемент изображения (пиксель) подвергался некоторому, как правило, однотипному преобразованию.


    И в результате получалось, что для изображения размерами M на N пикселей требуется MxN операций процессора. При значительных размерах изображения этот объем вычислений может оказаться критическим для одного устройства обработки информации. Поэтому, чтобы снизить вычислительную нагрузку алгоритма вполне резонно воспользоваться идеями и методами параллельных вычислений. Однако привлечение дорогостоящих параллельных суперкомпьютеров в данной задаче не является критически необходимым. Подобный класс задач можно попытаться решить с помощью обычной современной видеокарты, стоимость которой на несколько порядков меньше любого вычислительного кластера. Под точечными процессами будем понимать набор алгоритмов, которые подвергают обработке каждый пиксель изображения независимо от оста льных элементов. Примерами точечных пр оцессов могут выступать: приведение цветного изображения к оттенкам серого цвета, увеличение/уменьшение яркости и контраста, негативное преобразование, пороговое отсечение, соляризация и др. Следует отметить, что перечисленные точечные процессы мы будем рассматривать для изображений в оттенках серого цвета, так называемых grayscale. В подобных изображениях присутствуют только 256 оттенков какого-либо основного цвета. Как правило, используются оттенки серого цвета так, что палитра цветов содержит 256 "плавноизменяющихся" от черного к белому цвету интенсивностей. При этом черный цвет кодируется нулем, белый – числом 255. Таким образом, точечный процесс можно представить как некую функцию, определенную на целочисленном дискретном множестве [0…255] с таким же множеством значений.

    Преобразование просветления увеличивает или уменьшает значение яркости каждого пикселя в отдельности. Пусть I(x,y) – значение яркости пикселя (x,y) в изображении I. Тогда операция просветления на языке формул может быть выражена следующим образом: I(x,y)= I(x,y)+b, где b – постоянная яркости. Если b>0, то яркость в изображении будет увеличиваться (просветление); если же b<0, то яркость будет уменьшаться (затемнение).


    Могут возникнуть случаи, при которых значение выражения I(x,y)+b выйдет за пределы отрезка [0…255]. В этом случае значение, вышедшее за границы отрезка приводят к значению ближайшей границы, т.е. либо к 0, либо к 255. Графически операцию просветления можно описать следующими графиками функций.

    Графический процессор в задачах обработки изображений

    Рассмотрим произвольное изображение I в оттенках серого цвета. Введем массив H, содержащий 256 элементов: H[0…255]. Каждый i-й элемент этого массива будет содержать количество пикселей в изображении I со значением интенсивности i. Если визуализировать массив H в виде графика функции, то получим так называемую гистограмму интенсивностей яркости исходного изображения I. Вид гистограммы позволяет получить представление об общей яркости изображения. Гистограмма интенсивности является информативным инструментом при анализе общей яркости в изображении. Способ получения гистограммы интенсивности изображения можно записать на алгоритмическом языке следующим образом.

    Цикл по x от 0 до ШиринаИзображения-1 Цикл по y от 0 до ВысотаИзображения-1 ТелоЦикла k = I(x,y); H[k] = H[k] + 1; КонецТелоЦикла

    Ниже на рисунке приведены три гистограммы интенсивности различного класса.

    Графический процессор в задачах обработки изображений

    Гистограмма слева определяет изображения, в которых мало пикселей черного и белого цветов (низкоконтрастные изображения). Гистограмма посередине соответствует изображениям, в которых число черных и белых пикселей значительно превышает все остальные (высококонтрастные изображения). И гистограмма справа представляет изображения, в которых количество пикселей с различными интенсивностями цветов приблизительно одинаково (нормальноконтрастные изображения). Изменение контраста в изображении можно осуществить с помощью линейных преобразований. Для гистограмм, соответствующих низкоконтрастным изображениям, выделяют отрезок [Q1,Q2], на котором сосредоточена значительная часть интенсивностей. Затем данный отрезок [Q1,Q2] линейно отражают в отрезок [0…255] с помощью следующего преобразования: I(x,y)= 255*(I(x,y)-Q1)/(Q2-Q1). Аналогично можно получить формулу для уменьшения контраста в изображении.


    В этом случае преобразование будет иметь следующий вид: I(x,y)= R1+I(x,y)*(R2-R1)/255, где [R1,R2] – некий отрезок, в который отображается все множество значений интенсивностей. Графически операции увеличения и уменьшения контрастности в изображении можно представить следующим образом.

    Графический процессор в задачах обработки изображений

    Негативное преобразование инвертирует значения интенсивностей яркости так, что темные пиксели становятся светлыми и наоборот. Математически это задается довольно простой формулой:

    I(x,y)= 255- I(x,y), а графически это выглядит следующим образом.

    Графический процессор в задачах обработки изображений

    Пороговое отсечение (бинаризация) преобразует изображение в оттенках серого в изображение, в котором присутствует всего два цвета, как правило, белый и черный (бинарное). На языке формул бинаризация имеет такой вид: I(x,y)=0, если I(x,y)>p; I(x,y)=255, если I(x,y)
    Очень часто пороговое отсечение применяется как промежуточный шаг в задачах распознавания изображений для устранения ошибок сканирования и оцифровки.

    Получение изображения в оттенках серого из цветного также можно отнести к точечным процессам. Каждый пиксель цветного изображения представляет собой тройку байт, значения которых соответствуют весам красного, зеленого и синего цветов. Это так называемая цветовая модель RGB (Red, Green, Blue). Преобразование цветного изображения в оттенки серого осуществляется по следующей формуле:

    I=0.3*R+0.59*G+0.11*B, где I – значение интенсивности серого цвета, R, G, B - значения весов красного, зеленого и синего цветов соответственно.

    Ниже представлены результаты обработки исходного изображения пиксельным шейдером.

    sampler tex0;

    struct PS_INPUT { float2 base : TEXCOORD0; }; struct PS_OUTPUT { float4 diffuse : COLOR0; }; PS_OUTPUT Main (PS_INPUT input) { PS_OUTPUT output; float4 col = tex2D(tex0, input.base); ... return output; };
    Исходное изображение

    Графический процессор в задачах обработки изображений
    Приведение к оттенкам серого цвета

    float4 col = tex2D(tex0, input.base); float4 lum = float4(0.3, 0.59, 0.11, 0); output.diffuse = dot(lum,col);



    Графический процессор в задачах обработки изображений
    Увеличение яркости

    float4 col = tex2D(tex0, input.base); float4 lum = float4(0.3, 0.59, 0.11, 0); output.diffuse = dot(lum,col)+0.2f;


    Графический процессор в задачах обработки изображений
    Уменьшение яркости

    float4 col = tex2D(tex0, input.base); float4 lum = float4(0.3, 0.59, 0.11, 0); output.diffuse = dot(lum,col)-0.2f;


    Графический процессор в задачах обработки изображений
    Увеличение контраста

    float4 col = tex2D(tex0, input.base); float4 lum = float4(0.3, 0.59, 0.11, 0); float gray = dot(lum,col); float Q1 = 0.2f; float Q2 = 0.7f; if (gray > Q2) gray = 1.0f; else if (gray < Q1) gray = 0.0f; else gray = (gray - Q1)/(Q2-Q1); output.diffuse = gray;


    Графический процессор в задачах обработки изображений
    Уменьшение контраста

    float4 col = tex2D(tex0, input.base); float4 lum = float4(0.3, 0.59, 0.11, 0); float gray = dot(lum,col); float R1 = 0.2f; float R2 = 0.7f; gray = R1+gray*(R2-R1); output.diffuse = gray;



    Графический процессор в задачах обработки изображений
    Пороговое отсечение (бинаризация)

    float4 col = tex2D(tex0, input.base); float4 lum = float4(0.3, 0.59, 0.11, 0); float gray = dot(lum,col); float p = 0.4f; if (gray > p) gray = 1.0f; else if (gray < p) gray = 0.0f; output.diffuse = gray;


    Графический процессор в задачах обработки изображений
    Негативное преобразование

    float4 col = tex2D(tex0, input.base); float4 lum = float4(0.3, 0.59, 0.11, 0); float gray = dot(lum,col); output.diffuse = 1.0f-gray;


    Графический процессор в задачах обработки изображений
    <


    Рассмотрим теперь некоторые методы обработки изображения с использованием пространственных процессов. В этом случае элемент изображения получает новое значение на основе группы элементов, примыкающих к данному. Область (окрестность) примыкания представляет собой группу элементов изображения использующаяся в пространственных процессах. Как правило, область примыкания есть квадратная матрица нечетной размерности с центром в обрабатываемом элементе.

    Графический процессор в задачах обработки изображений

    Пространственная частота изображения – скорость изменения яркости по координатам. Говорят, что присутствует высокая частота в изображении, если яркость меняется очень сильно. Одной из центральных задач в обработке изображений является построение пространственного фильтра. Фильтр позволяет усилить или ослабить компоненты различной частоты. Пространственный фильтр – процесс, который способен выделить (подчеркнуть) компоненты определенной частоты. Двумерный фильтр устроен следующим образом. Берется матрица размером 3х3, 5х5, 7х7 и т.д. и на ней определяется некоторая функция Упомянутая матрица называется окном или апертурой, а заданная на нем функция – весовой или функцией окна. Каждому элементу окна соответствует число, называемое весовым множителем. Совокупность всех весовых множителей и составляет весовую функцию. Нечетные размеры апертуры объясняются однозначностью определения центрального элемента. Фильтрация осуществляется перемещением окна (апертуры) фильтра по изображению. В каждом положении апертур

    ФЄ ы выполняются однотипные действия, которые определяют так называемый отклик фильтра. Весовая функция в процессе перемещения остается неизменной. В каждом положении окна происходит операция свертки – линейная комбинация значений элементов изображения: Графический процессор в задачах обработки изображений, где Графический процессор в задачах обработки изображений - элементы области примыкания, Графический процессор в задачах обработки изображений - весовые множители, Графический процессор в задачах обработки изображений - новое значение пикселя. При каждом положении окна весовая функция поэлементно умножается на значение соответствующих пикселей исходного изображения и произведения суммируются. Полученная сумма называется откликом фильтра и присваивается тому пикселю нового изображения, который соответствует положению центра окна.


    Низкочастотный фильтр – процесс, который ослабляет высокочастотные компоненты и усиливает роль низкочастотных.

    Сглаживание изображения реализуется с помощью следующих ядер.

    Графический процессор в задачах обработки изображений

    Следует заметить, что общая яркость исходного изображения и результирующего будет одинаковой.

    Фильтры высокой частоты применяются для выделения таких деталей, как контуры, границы или для повышения резкости изображения. Каждый скачок яркости и каждый контур представляют собой интенсивные детали, связанные с повышенными частотами. С помощью высокочастотного фильтра можно так видоизменить изображение, чтобы скачки яркости на контурах будут сильно подчеркнуты, а в предельном случае вообще останутся только контуры. Ниже приведены примеры ядер высокочастотных фильтров.

    Графический процессор в задачах обработки изображений

    Медианный фильтр – пространственный процесс, который не подпадает под категорию свертки. Усредненное фильтрование использует значения элементов, содержащихся в области примыкания, для определения нового значения. Фильтр располагает элементы области примыкания в возрастающем порядке и отбирает среднее значение.

    Графический процессор в задачах обработки изображений

    Результатом усредненного фильтра является то, что любой случайный шум, содержащийся в изображении, будет устранен. Это происходит потому, что любое случайное резкое изменение интенсивности элемента в пределах области примыкания, будет сортироваться, т.е. будет помещено либо в начало, либо в конец отсортированного списка.

    Другим пространственным процессом, который можно продемонстрировать, используя свертку, является усиление края. В отличие от задачи обострения контуров (высокочастотный фильтр) здесь основной целью является не улучшение изображения, а наоборот, контуры должны быть отделены от всего изображения так, чтобы выходное изображение состояло только из контуров. Рассмотрим основные методы усиления края.

    Метод усиления края по Лапласу не зависит от направления краев, и высвечиваются все направления. Ниже приведены три лапласиана.

    Графический процессор в задачах обработки изображений

    Метод усиления края с помощью оператора Собеля рассматривает два различных ядра свертки:

    Графический процессор в задачах обработки изображений

    Исходя из этих сверток, вычисляется величина и направление краев.


    В качестве отклика данного фильтра выступает величина Графический процессор в задачах обработки изображений, где P и Q - отклики ядер Графический процессор в задачах обработки изображений и Графический процессор в задачах обработки изображений соответственно.

    Метод усиления края с помощью оператора Превита также использует два ядра:

    Графический процессор в задачах обработки изображений

    Результат работы оператора Превита есть max{P,Q}, где P и Q - отклики ядер Графический процессор в задачах обработки изображений и Графический процессор в задачах обработки изображений соответственно.

    Метод преобразования реализующий эффект тиснения на изображении. Результирующее изображение выглядит как будто "выдавленным" или "вдавленным". Фильтр такого преобразования имеет вид: Графический процессор в задачах обработки изображений К отклику ядра прибавляется константа яркости, как правило, это 128.

    Как нам известно, обращение к элементам текстуры в пиксельном шейдере производится с помощью текстурных координат. Левый верхний тексель имеет текстурные координаты (0,0), левый нижний – координаты (0,1), правый верхний – координаты (1,0), правый нижний – координаты (1,1)

    Графический процессор в задачах обработки изображений

    Задача состоит в том, чтобы для произвольного текселя изображения, имеющего текстурные координаты (u,v), определить значения текстурных координат восьми его соседей. Пусть у нас количество текселей в каждой строке будет W, а количество текселей в каждом столбце – H. В силу того, что тексели расположены равномерно (на одинаковом расстоянии друг от друга), можно вычислить шаг приращения du и dv в текстурных координатах по горизонтали и вертикали соответственно. Итак, Графический процессор в задачах обработки изображений, где W и H – ширина и высота изображения соответственно. Например, для изображения, представленного выше, W = 20 пикселей, H = 9 пикселей, и шаг по горизонтали Графический процессор в задачах обработки изображений, а шаг по вертикали Графический процессор в задачах обработки изображений. Таким образом, для произвольного текселя, имеющего текстурные координаты (u,v), текстурные координаты его восьми соседей будут следующие: (u-du, v-dv), (u, v-dv), (u+du, v-dv), (u-du, v), (u+du, v), (u-du, v+dv), (u, v+dv), (u+du, v+dv), как показано на приведенном ниже рисунке.

    Графический процессор в задачах обработки изображений

    Ниже приведен пример пиксельного шейдера, который реализует метод усиления границ на изображении с помощью оператора Собеля и метод тиснения, а также примеры изображений.

    sampler tex0;

    struct PS_INPUT { float2 base : TEXCOORD0; };

    struct PS_OUTPUT { float4 diffuse : COLOR0; };


    PS_OUTPUT Main (PS_INPUT input) { PS_OUTPUT output; const float W =320.0f; const float H =240.0f; const float du=1.0f/(W-1); const float dv=1.0f/(H-1); const float2 c[9] = { float2(-du, -dv), float2(0.0f, -dv), float2(du, -dv), float2(-du, 0.0f), float2(0.0f, 0.0f), float2(du, 0.0f), float2(-du, dv), float2(0.0f, dv), float2(du, dv) };

    float3 col[9]; for (int i=0; i<9; i++) { col[i] = tex2D(tex0, input.base+c[i]); }

    float lum[9]; float3 gray = (0.30f, 0.59f, 0.11f) ; for (int i=0; i<9; i++) { lum[i] = dot(col[i], gray); }

    float res1 = 0.0f; float res2 = 0.0f; const float sobel1[9] = { 1, 2, 1, 0, 0, 0, -1, -2, -1}; const float sobel2[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1}; const float tisnenie[9] = {0, 1, 0, -1, 0, 1, 0, -1, 0}; for (int i=0; i<9; i++) { res1+=lum[i]*sobel1[i]; res2+=lum[i]*sobel2[i]; res+=lum[i]*tisnenie[i]; } output.diffuse = sqrt(res1*res1+res2*res2); //output.diffuse = res+0.5f; return output; };

    Пример 6.1.
    Исходное изображениеОператор СобеляМетод тиснения


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений


    Графический процессор в задачах обработки изображений
    Графический процессор в задачах обработки изображений

    Использование шейдеров с помощью языка HLSL

    До появления на свет восьмой версии библиотеки DirectX графический конвейер представлял собой некую модель "черного ящика", когда программист мог загружать в него исходные графические данные и настраивать фиксированное количество параметров (состояний). Такой фиксированный подход связывал руки разработчикам в реализации различных спецэффектов при программировании трехмерной графики. Данный недостаток был преодолен с появлением восьмой версии графической библиотеки DirectX. Основным нововведением в ней стало появление программируемых элементов графического конвейера. Были введены так называемые вершинные шейдеры для замены блока трансформации вершин и расчета освещенности, и пиксельные шейдеры для замены блока мультитекстурирования. Теперь программист мог сам задавать правила (законы) преобразования вершин трехмерной модели в вершинном шейдере и определять способы смешивания цвета пикселя и текстурных цветов. Таким образом вершинный шейдер представляет собой небольшую п рограмму (набор инструкци й), которая оперирует с вершинными атрибутами трехмерного объекта. Пиксельный шейдер предназначен для обработки элементарных фрагментов (пикселей). Ниже представлена схема графического конвейера, где показано какой этап обработки вершин заменяется вершинными шейдерами.
    Использование шейдеров с помощью языка HLSL

    Изначально шейдеры писались на языке программирования, близкого к ассемблеру. С выходом девятой версии библиотеки DirectX появилась возможность создавать (программировать) шейдеры с использованием высокоуровневого языка программирования HLSL (High-Level Shader Language), разработанного компанией Microsoft. Преимущества высокоуровневого языка программирования перед низкоуровневым очевидны:
  • Написание программ (кодирование) занимает меньше времени (можно посветить больше времени разработке алгоритма)
  • Программы на языке HLSL более читабельны и удобнее в отладке.
  • Компилятор HLSL создает более оптимизированный код чем программист.
  • Возможность компилировать программу под любую версию шейдеров.

  • Рассмотрим сначала основные шаги использования вершинных шейдеров в библиотеке Direct3D с использованием языка HLSL.
    Вообще говоря, вершинные шейдеры могут эмулироваться программным способом. Это означает, что вся обработка (обсчет) вершин будет производиться с помощью центрального процессора (CPU) компьютера. Программно это достигается путем указания в четвертом параметре функции создания устройства вывода, флага D3DCREATE_SOFTWARE_VERTEXPROCESSING. В случае если возможности видеокарты позволяют использование шейдеров, то указывается константа D3DCREATE_HARDWARE_VERTEXPROCESSING.

    Первым шагом при работе в вершинными шейдерами необходимо задать формат вершины. Теперь это проделывается не через набор FVF флагов, а с помощью структуры D3DVertexElement9. Нужно заполнить массив типа D3DVertexElement9, каждый элемент которого представляет структуру, состоящую из шести полей. Первое поле указывает номер потока вершин, и как правило, здесь передается ноль, если используется один поток. Второе поле задает для атрибута вершины смещение в байтах от начала структуры. Так, например, если вершина имеет атрибуты позиции и нормали, то смещение для первого из них (позиции) будет 0, а для второго (нормаль) – 12, т.к. объем памяти для первого атрибута есть 3*4=12 байт. Третье поле определяет тип данных для каждого атрибута вершины. Наиболее часто используемые приведены ниже:

    D3DDECLTYPE_FLOAT1 D3DDECLTYPE_FLOAT2 D3DDECLTYPE_FLOAT3 D3DDECLTYPE_FLOAT4 D3DDECLTYPE_D3DCOLOR.

    Четвертое поле задает метод тесселяции (разбиения сложной трехмерной поверхности на треугольники). Здесь, как правило, передают константу D3DDECLMETHOD_DEFAULT. Пятое поле указывает на то, в качестве какого компонента планируется использовать данный вершинный атрибут. Наиболее используемые константы представлены ниже:

    D3DDECLUSAGE_POSITION, D3DDECLUSAGE_NORMAL, D3DDECLUSAGE_TEXCOORD, D3DDECLUSAGE_COLOR.

    И последнее, шестое поле определяет индекс для одинаковых типов вершинных атрибутов. Например, если имеется три вершинных атрибута, описанные как D3DDECLUSAGE_NORMAL, то для первого из них нужно задать индекс 0, для второго – 1, для третьего – 2.


    Ниже приведен пример описания вершины, содержащей положение и цвет с помощью массива элементов D3DVertexElement9.

    C++

    D3DVERTEXELEMENT9 declaration[] = { { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, { 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, D3DDECL_END() };
    Pascal

    declaration: array [0..2] of TD3DVertexElement9 = ( (Stream: 0; Offset: 0; _Type: D3DDECLTYPE_FLOAT3; Method: D3DDECLMETHOD_DEFAULT; Usage: D3DDECLUSAGE_POSITION; UsageIndex: 0), (Stream: 0; Offset: 12; _Type: D3DDECLTYPE_D3DCOLOR; Method: D3DDECLMETHOD_DEFAULT; Usage: D3DDECLUSAGE_COLOR; UsageIndex: 0), (Stream: $FF; Offset: 0; _Type: D3DDECLTYPE_UNUSED; Method: TD3DDeclMethod(0); Usage: TD3DDeclUsage(0); UsageIndex: 0) );
    После описания формата вершины требуется получить указатель на интерфейс IDirect3DVertexDeclaration9. Это реализуется через вызов метода CreateVertexDeclaration() интерфейса IDirect3DDevice9. Первый параметр данного метода определяет массив элементов типа D3DVERTEXELEMENT9, второй аргумент – возвращаемый результат.

    C++

    LPDIRECT3DVERTEXDECLARATION9 VertexDeclaration = NULL; device->CreateVertexDeclaration( declaration, &VertexDeclaration );
    Pascal

    var VertexDeclaration: IDirect3DVertexDeclaration9; ... device.CreateVertexDeclaration( @declaration, VertexDeclaration );
    Установка формата вершин без использования вершинных шейдеров производилась через вызов метода SetFVF(). Теперь же для этого предназначен метод SetVertexDeclaration() интерфейса IDirect3DDevice9. Как правило, данный метод вызывается в процедуре Render.

    C++device->SetVertexDeclaration( VertexDeclaration );
    Pascaldevice.SetVertexDeclaration(VertexDeclaration);
    Следующий шаг – компиляция вершинного шейдера. Данный шаг реализуется с помощью вызова функции D3DXCompileShaderFromFile().

    Первый параметр функции задает строку, в которой содержится имя файла вершинного шейдера.

    Второй и третий параметры являются специфическими и, как правило, здесь передаются значения NULL.


    Четвертый параметр – строка, определяющая название функции в шейдере или так называемая точка входа в программу.

    Пятый параметр – строка, задающая версию шейдера. Для вершинных шейдеров указывают одну из следующих строковых констант: vs_1_1, vs_2_0, vs_3_0. Шестой параметр определяет набор флагов. Здесь могут быть переданы следующие константы:

    D3DXSHADER_DEBUG – указание компилятору выдавать отладочную информацию;

    D3DXSHADER_SKIPVALIDATION – указание компилятору не производить проверку кода шейдера на наличие ошибок;

    D3DXSHADER_SKIPOPTIMIZATION – указание компилятору не производить оптимизацию кода шейдера. Можно указать значение ноль.

    Седьмой параметр – переменная, типа ID3DXBuffer, которая содержит указатель на откомпилированный код шейдера.

    Восьмой параметр – переменная, содержащая указатель на буфер ошибок и сообщений.

    И последний, девятый параметр – переменная типа ID3DXConstantTable, в которую записывается указатель на таблицу констант. Через данный указатель производится "общение" с константами в шейдере.

    Ниже приведен пример компиляции вершинного шейдера, хранящегося в файле vertex.vsh.

    C++

    LPD3DXBUFFER Code = NULL; LPD3DXBUFFER BufferErrors = NULL; LPD3DXCONSTANTTABLE ConstantTable = NULL; ... D3DXCompileShaderFromFile( "vertex.vsh", NULL, NULL, "main", "vs_1_1", 0, &Code, &BufferErrors, &ConstantTable );
    Pascal

    var Code: ID3DXBuffer; BufferErrors: ID3DXBuffer; ConstantTable: ID3DXConstantTable; ... D3DXCompileShaderFromFile('vertex.vsh', nil, nil, 'main', 'vs_1_1', 0, @Code, @BufferErrors, @ConstantTable);
    Следующий шаг – получение указателя на откомпилированный код шейдера. Для этого используется метод CreateVertexShader() интерфейса IDirect3DDevice9. Метод имеет два параметра: указатель на буфер, в котором хранится скомпилированный код шейдера и переменная интерфейсного типа IDirect3DVertexShader9, в которую будет помещен результат вызова.

    C++

    LPD3DXBUFFER Code = NULL; LPDIRECT3DVERTEXSHADER9 VertexShader = NULL; ... device->CreateVertexShader( (DWORD*)Code->GetBufferPointer(), &VertexShader );
    Pascal

    var Code: ID3DXBuffer; VertexShader: IDirect3DVertexShader9; ... device.CreateVertexShader(Code.GetBufferPointer, VertexShader);
    <


    И заключительный шаг – установка вершинного шейдера, реализуемая через вызов метода SetVertexShader() интерфейса IDirect3DDevice9. Как правило, данный метод вызывается в процедуре вывода сцены (Render).

    C++

    LPDIRECT3DVERTEXSHADER9 VertexShader = NULL; ... device->SetVertexShader( VertexShader );
    Pascal

    var VertexShader: IDirect3DVertexShader9; ... device.SetVertexShader(VertexShader);
    Теперь разберем, что из себя представляет шейдер на языке HLSL. Вершинный шейдер есть не что иное, как обычный текстовый файл, содержащий программный код. Этот программный код можно разбить на несколько секций:

  • Секция глобальных переменных и констант
  • Секция, описывающая входные данные вершины
  • Секция, описывающая выходные данные вершины
  • Главная процедура в шейдере (точка входа)


  • В секции глобальных переменных и констант описываются данные, которые не содержатся в вершинных атрибутах: матрицы преобразований, положения источников света и др. Ниже приведен пример описания глобальной матрицы и статичной переменной.

    float4x4 WorldViewProj; static float4 col = {1.0f, 1.0f, 0.0f, 1.0f};

    В секции, описывающей входные данные, определяется входная структура вершинных атрибутов. Например, для вершин, которые содержат позицию и цвет эта структура может выглядеть так.

    struct VS_INPUT { float4 position : POSITION; float4 color0 : COLOR0; };

    Аналогично определяется выходная структура данных шейдера.

    struct VS_OUTPUT { float4 position : POSITION; float4 color0 : COLOR0; };

    Используемые здесь семантические конструкции (POSITION и COLOR0) указывают на принадлежность того или иного атрибута вершины.

    Так же как и в программах на языке C++, программа на языке HLSL должна иметь точку входа (главную процедуру). Здесь точка входа может быть описана следующим образом.

    VS_OUTPUT main( VS_INPUT IN ) { VS_OUTPUT OUT; … return OUT; }

    Вообще говоря, использование входных и выходных структур не является обязательным в языке HLSL. Можно использовать привычный для любого программиста подход передачи параметров без структур.


    float4 main(in float2 tex0 : TEXCOORD0, in float2 tex1 : TEXCOORD1) : COLOR { return …; }

    Разберем теперь, как осуществляется преобразование вершины в вершинном шейдере. Как мы уже знаем, трансформация вершины осуществляется путем умножения вектор-строки, описывающей компоненты вершины, на матрицу преобразования. В языке HLSL данный шаг осуществляется с помощью функции mul.

    OUT.position = mul( IN.position, WorldViewProj );

    Таким образом, каждая вершина трехмерной модели подвергается обработке с помощью данного преобразования, а результат передается дальше по конвейеру. Ниже приведен пример полного текста кода шейдера.

    float4x4 WorldViewProj;

    struct VS_INPUT { float4 position : POSITION; float4 color0 : COLOR0; ;

    struct VS_OUTPUT { float4 position : POSITION; float4 color0 : COLOR0; };

    VS_OUTPUT main( VS_INPUT IN ) { VS_OUTPUT OUT; OUT.position = mul( IN.position, WorldViewProj ); OUT.color0 = IN.color0; return OUT; }

    Теперь необходимо рассмотреть каким образом происходит установка значений констант в шейдере из программы. Как мы уже видели, при вызове метода компиляции шейдера (D3DXCompileShaderFromFile), в последнюю переменную данной функции помещается ссылка на так называемую таблицу констант. Именно с помощью данного указателя и происходит присваивание значений константам в шейдере. Реализуется это с помощью вызова методов SetXXX интерфейса ID3DXConstantTable, где XXX – "заменяется" на следующие выражения: Bool, Float, Int, Matrix, Vector. Данные методы имеют три параметра: первый – указатель на устройство вывода, второй – наименование константы в шейдере, и третий – устанавливаемое значение. Так, например, установка значения для матрицы преобразования (WorldViewProj) в приведенном выше примере осуществляется следующим образом.

    C++

    D3DXMATRIX matWorld, matView, matProj, tmp; D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f ); D3DXVECTOR3 positionCamera, targetPoint, worldUp; positionCamera = D3DXVECTOR3(2.0f, 2.0f, -2.0f); targetPoint = D3DXVECTOR3(0.0f, 0.0f, 0.0f); worldUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f); D3DXMatrixLookAtLH(&matView, &positionCamera, &targetPoint, &worldUp); D3DXMatrixRotationY(&matWorld, angle); tmp = matWorld * matView * matProj; ConstantTable->SetMatrix( device, "WorldViewProj", &tmp );
    Pascal

    var matWorld, matView, matProj, tmp: TD3DMatrix; positionCamera, targetPoint, worldUp : TD3DXVector3; ... positionCamera:=D3DXVector3(2,2,-2); targetPoint:=D3DXVector3(0,0,0); worldUp:=D3DXVector3(0,1,0); D3DXMatrixLookAtLH(matView, positionCamera, targetPoint, worldUp); D3DXMatrixPerspectiveFovLH(matProj, PI/4, 1, 1, 100); D3DXMatrixRotationY(matWorld, angle); D3DXMatrixMultiply(tmp, matWorld, matView); D3DXMatrixMultiply(tmp, tmp, matProj); ConstantTable.SetMatrix(device, 'WorldViewProj', tmp);
    <


    Ниже приведены примеры вызова каждого метода.

    C++

    LPD3DXCONSTANTTABLE ConstantTable = NULL;

    bool b = true; ConstantTable->SetBool( device, "flag", b );

    float f = 3.14f; ConstantTable->SetFloat( device, "pi", f );

    int x = 4; ConstantTable->SetInt( device, "num", x );

    D3DXMATRIX m; ... ConstantTable->SetMatrix( device, "mat", &m );

    D3DXVECTOR4 v(1.0f, 2.0f, 3.0f, 4.0f); ConstantTable->SetVector( device, "vec", &v );
    Pascal

    var b: Boolean; f: Single; x: Integer; m: TD3DMatrix; v: TD3DXVector4; ConstantTable: ID3DXConstantTable; ... b := true; ConstantTable.SetBool(device, 'flag', b);

    f:=3.14; ConstantTable.SetFloat(device, 'pi', f);

    x := 4; ConstantTable.SetInt(device, 'num', x);

    m._11:=1; ... ConstantTable.SetMatrix(device, 'mat', m);

    v := D3DXVector4(1,2,3,4); ConstantTable.SetVector(device, 'vec', v);
    В качестве примера использования вершинных шейдеров рассмотрим реализацию эффекта скручивания трехмерного объекта вдоль одной из координатных осей. Данный эффект осуществляется с помощью смешения двух матриц преобразования (matrix blending). Основная идея такого преобразования объекта, заданного своими вершинами, может быть выражена с помощью следующей формулы: Использование шейдеров с помощью языка HLSL, где Использование шейдеров с помощью языка HLSL - координаты вершины, Использование шейдеров с помощью языка HLSL - вес вершины, Использование шейдеров с помощью языка HLSL - матрицы преобразования. Как правило, вес вершине Использование шейдеров с помощью языка HLSL приписывается линейно изменяющийся вдоль одной из осей. В результате, на часть точек объекта большее влияние оказывает матрица Использование шейдеров с помощью языка HLSL, на другую часть – матрица Использование шейдеров с помощью языка HLSL. Пусть у нас в качестве объекта выступает единичный куб, состоящий из маленьких треугольников (их количество можно регулировать) и ни жнее основание которого расположено в плоскости y=0, как показано на рисунке ниже.

    Использование шейдеров с помощью языка HLSL

    В качестве веса вершины пусть выступает значение координаты y, а матрицы Использование шейдеров с помощью языка HLSL и Использование шейдеров с помощью языка HLSL задают матрицы поворота вокруг оси OY на углы 30 и -30 градусов соответственно. Результат скручивания объекта (куба) по приведенной выше формуле показаны ниже.

    Использование шейдеров с помощью языка HLSL

    При этом код вершинного шейдера будет выглядеть следующим образом.


    float4x4 M1; float4x4 M2;

    struct VS_INPUT { float4 position : POSITION; float4 color0 : COLOR0; };

    struct VS_OUTPUT { float4 position : POSITION; float4 color0 : COLOR0; };

    VS_OUTPUT main( VS_INPUT IN ) { VS_OUTPUT OUT; float4x4 m = (1-IN.position.y)*M1 + IN.position.y*M2; OUT.position = mul( IN.position, m ); OUT.color0 = IN.position; return OUT; }

    Присутствующие в шейдере матрицы преобразования Использование шейдеров с помощью языка HLSL и Использование шейдеров с помощью языка HLSL устанавливаются через вызывающую программу с помощью таблицы констант.

    C++

    D3DXMATRIX matWorld1, matWorld2, matView, matProj, M1, M2; LPD3DXCONSTANTTABLE ConstantTable = NULL;

    D3DXMatrixRotationY(&matWorld1, 30.0f*D3DX_PI/4); M1 = matWorld1 * matView * matProj; ConstantTable->SetMatrix( device, "M1", &M1 );

    D3DXMatrixRotationY(&matWorld2, -30.0f*D3DX_PI/4); M2 = matWorld2 * matView * matProj; ConstantTable->SetMatrix( device, "M2", &M2 );
    Pascal

    var matWorld1, matWorld2, matView, matProj, M1, M2: TD3DMatrix; ConstantTable: ID3DXConstantTable; ... D3DXMatrixRotationY(matWorld1, 30*pi/180); D3DXMatrixMultiply(M1, matWorld1, matView); // M1 = matWorld1 * matView D3DXMatrixMultiply(M1, M1, matProj); // M1 = M1 * matProj ConstantTable.SetMatrix(device, 'M1', M1);

    D3DXMatrixRotationY(matWorld2, - 30*pi/180); D3DXMatrixMultiply(M2, matWorld2, matView); D3DXMatrixMultiply(M2, M2, matProj); ConstantTable.SetMatrix(device, 'M2', M2);
    Следует заметить, что подобное преобразование никак не зависит от степени детализации (количества треугольников) исходного единичного куба. Кроме того, искажению может быть подвергнут абсолютно любой трехмерный объект. Ниже представлен пример трансформации чайника с помощью метода смешения матриц преобразования.

    Использование шейдеров с помощью языка HLSL

    Рассмотрим теперь необходимые шаги для работы с пиксельным шейдером. В отличие от вершинных шейдеров, пиксельные шейдеры не могут эмулироваться центральным процессором. Поэтому если видеокарта не поддерживает пиксельных шейдеров, значит обработка элементарных фрагментов (пикселей) будет производится по жестко заданному правилу.


    Как мы уже говорили, пиксельный шейдер представляет собой небольшую программу (процедуру) для обработки каждого пикселя. Первым шагом необходимо объявить переменную интерфейсного типа IDirect3DPixelShader9, которая отвечает за работу пиксельного шейдера из программы.

    C++LPDIRECT3DPIXELSHADER9 PixelShader = NULL;
    Pascalvar PixelShader: IDirect3DPixelShader9;
    Компиляция пиксельного шейдера осуществляется также с помощью функции D3DXCompileShaderFromFile().

    C++

    LPD3DXBUFFER Code = NULL; LPD3DXBUFFER BufferErrors = NULL; LPD3DXCONSTANTTABLE ConstantTable = NULL;

    D3DXCompileShaderFromFile( "pixel.psh", NULL, NULL, "main", "ps_1_0", 0, &Code, &BufferErrors, &ConstantTable );
    Pascal

    var Code: ID3DXBuffer; BufferErrors: ID3DXBuffer; ConstantTable: ID3DXConstantTable; ...

    D3DXCompileShaderFromFile('pixel.psh', nil, nil, 'Main', 'ps_1_0', 0, @Code, @BufferErrors, @ConstantTable);

    Следующий шаг – получение указателя на откомпилированный код шейдера. Реализуется этот шаг вызовом метода CreatePixelShader() интерфейса IDirect3DDevice9.

    C++

    LPDIRECT3DPIXELSHADER9 PixelShader = NULL;

    device->CreatePixelShader( (DWORD*)Code->GetBufferPointer(), &PixelShader );
    Pascal

    var PixelShader: IDirect3DPixelShader9; ... device.CreatePixelShader(Code.GetBufferPointer, PixelShader);
    Следующий шаг заключается в установке пиксельного шейдера в функции рендеринга. Осуществляется это путем вызова метода SetPixelShader() интерфейса IDirect3DDevice9, где в качестве параметра передается указатель на пиксельный шейдер.

    C++device->SetPixelShader( PixelShader );
    Pascaldevice.SetPixelShader(PixelShader);
    Рассмотрим теперь что из себя представляет код пиксельного шейдера на языке HLSL. Как и в случае с вершинным шейдером, код пиксельного шейдера можно формально разбить на четыре раздела: область глобальных переменных, разделы описания входной и выходной структур и основная процедура обработки. В самом простейшем случае пиксельный шейдер – процедура, приминающая на вход цвет пикселя и выдающая также цвет пикселя.


    struct PS_INPUT { float4 color: COLOR; };

    struct PS_OUTPUT { float4 color : COLOR; };

    PS_OUTPUT main (PS_INPUT input) { PS_OUTPUT output; output.color = input.color; return output; };

    В данном случае пиксельный шейдер фактически просто "проталкивает" пиксель дальше по графическому конвейеру, не подвергая его никакой обработке. Рассмотрим несколько способов возможной обработки точек в пиксельном шейдере на примере плоского цветного треугольника.

    "проталкивание" пикселяoutput.color = input.color;

    Использование шейдеров с помощью языка HLSL
    инвертирование цветовoutput.color = 1-input.color;

    Использование шейдеров с помощью языка HLSL
    увеличение яркостиoutput.color = 2*input.color;

    Использование шейдеров с помощью языка HLSL
    уменьшение яркостиoutput.color = 0.5*input.color;

    Использование шейдеров с помощью языка HLSL
    блокирование цветового каналаoutput.color = input.color;

    output.color.r = 0;


    Использование шейдеров с помощью языка HLSL
    сложная обработкаoutput.color.r=0.5*input.color.r;

    output.color.g=2.0*input.color.g;

    output.color.b=input.color.b*input.color.b;


    Использование шейдеров с помощью языка HLSL
    Так как пиксельные шейдеры предназначены для замены блока мультитекстурирования, то рассмотрим каким образом происходит обработка текселей текстур. Для работы с текстурами в пиксельном шейдере предусмотрены так называемые семплеры. Семплер представляет собой текстуру, и набор правил (режим адресации текстурных координат, их индекс и тип установленной фильтрации текстур) для извлечения определенного текселя. Выбор текселя осуществляется с помощью функции tex2D(), которая имеет два параметра: название семплера и текстурные координаты. Ниже приведен пример пиксельного шейдера, в котором присутствует функция выборки текселя из текстуры.

    sampler tex0;

    struct PS_INPUT { float2 base : TEXCOORD0; };

    struct PS_OUTPUT { float4 diffuse : COLOR0; };

    PS_OUTPUT Main (PS_INPUT input) { PS_OUTPUT output; output.diffuse = tex2D(tex0, input.base); return output; };

    В первой строке шейдера объявляется семплер (tex0). Операция выбора текселя из семплера называют семплированием. Следует заметить, что входная структура шейдера (PS_INPUT) содержит лишь текстурные координаты пикселя. Вообще говоря, в пиксельный шейдер можно передавать те данные, которые программист считает нужными (цвет вершины, вектор нормали, положение источника света и т.д.).


    Например, ниже приводится пример, в котором входная структура пиксельного шейдера содержит цвет и двое текстурных координат.

    struct PS_INPUT { float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 color : COLOR0; };

    Пусть у нас вершина описана через положение на плоскости (преобразованная вершина), цвет и две текстурные координаты:

    C++

    struct MYVERTEX { FLOAT x, y, z, rhw; DWORD color; FLOAT u1, v1; FLOAT u2, v2; } #define MY_FVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX2);
    Pascal

    type MyVertex = packed record x, y, z, rhw: Single; color: DWORD; u1,v1: Single; u2,v2: Single; end; const MY_FVF = D3DFVF_XYZRHW or D3DFVF_DIFFUSE or D3DFVF_TEX2;
    Рассмотрим пример мультитекстурирования на примере следующих исходных данных. Две заданные текстуры и способ закраски примитива (квадрата) показаны ниже.



    Использование шейдеров с помощью языка HLSL


    Использование шейдеров с помощью языка HLSL


    Использование шейдеров с помощью языка HLSL
    Текстура1Текстура2Закраска квадрата
    Пример пиксельного шейдера, реализующего мультитекстурирование показан ниже.

    sampler tex0; sampler tex1;

    struct PS_INPUT { float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 color: COLOR0; };

    struct PS_OUTPUT { float4 diffuse : COLOR0; };

    PS_OUTPUT Main (PS_INPUT input) { PS_OUTPUT output; float4 texel0 = tex2D(tex0, input.uv0); float4 texel1 = tex2D(tex1, input.uv1); output.diffuse = ...; return output; };

    Некоторые способы взаимодействия двух этих поверхностей (текстуры и цветного квадрата) представлены в таблице.

    output.diffuse = texel0*texel1;

    Использование шейдеров с помощью языка HLSL
    output.diffuse = texel0*texel1+input.color;

    Использование шейдеров с помощью языка HLSL
    output.diffuse = texel0+texel1*input.color;

    Использование шейдеров с помощью языка HLSL
    output.diffuse = texel0*texel1*input.color;

    Использование шейдеров с помощью языка HLSL

    PS_OUTPUT output; const float

    sampler tex0;
    struct PS_INPUT { float2 base : TEXCOORD0; };
    struct PS_OUTPUT { float4 diffuse : COLOR0; };
    PS_OUTPUT Main (PS_INPUT input) { PS_OUTPUT output; const float W =320.0f; const float H =240.0f; const float du=1.0f/(W-1); const float dv=1.0f/(H-1); const float2 c[9] = { float2(-du, -dv), float2(0.0f, -dv), float2(du, -dv), float2(-du, 0.0f), float2(0.0f, 0.0f), float2(du, 0.0f), float2(-du, dv), float2(0.0f, dv), float2(du, dv) };
    float3 col[9]; for (int i=0; i<9; i++) { col[i] = tex2D(tex0, input.base+c[i]); }
    float lum[9]; float3 gray = (0.30f, 0.59f, 0.11f) ; for (int i=0; i<9; i++) { lum[i] = dot(col[i], gray); }
    float res1 = 0.0f; float res2 = 0.0f; const float sobel1[9] = { 1, 2, 1, 0, 0, 0, -1, -2, -1}; const float sobel2[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1}; const float tisnenie[9] = {0, 1, 0, -1, 0, 1, 0, -1, 0}; for (int i=0; i<9; i++) { res1+=lum[i]*sobel1[i]; res2+=lum[i]*sobel2[i]; res+=lum[i]*tisnenie[i]; } output.diffuse = sqrt(res1*res1+res2*res2); //output.diffuse = res+0.5f; return output; };
    Пример 6.1.
    Закрыть окно




    sampler tex0;
    struct PS_INPUT
    {
    float2 base : TEXCOORD0;
    };
    struct PS_OUTPUT
    {
    float4 diffuse : COLOR0;
    };
    PS_OUTPUT Main (PS_INPUT input)
    {
    PS_OUTPUT output;
    const float W =320.0f;
    const float H =240.0f;
    const float du=1.0f/(W-1);
    const float dv=1.0f/(H-1);
    const float2 c[9] = {
    float2(-du, -dv), float2(0.0f, -dv), float2(du, -dv),
    float2(-du, 0.0f), float2(0.0f, 0.0f), float2(du, 0.0f),
    float2(-du, dv), float2(0.0f, dv), float2(du, dv)
    };

    float3 col[9];
    for (int i=0; i<9; i++) {
    col[i] = tex2D(tex0, input.base+c[i]);
    }

    float lum[9];
    float3 gray = (0.30f, 0.59f, 0.11f) ;
    for (int i=0; i<9; i++) {
    lum[i] = dot(col[i], gray);
    }

    float res1 = 0.0f;
    float res2 = 0.0f;
    const float sobel1[9] = { 1, 2, 1, 0, 0, 0, -1, -2, -1};
    const float sobel2[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1};
    const float tisnenie[9] = {0, 1, 0, -1, 0, 1, 0, -1, 0};
    for (int i=0; i<9; i++) {
    res1+=lum[i]*sobel1[i];
    res2+=lum[i]*sobel2[i];
    res+=lum[i]*tisnenie[i];
    }
    output.diffuse = sqrt(res1*res1+res2*res2);
    //output.diffuse = res+0.5f;
    return output;
    };

    Файлы эффектов

    В состав библиотеки Direct3D входит набор средств, которые предоставляют некий оберточный механизм для работы с вершинными и пиксельными шейдерами, установкой текстурных состояний и константами. Данная возможность реализуется с помощью интерфейса ID3DXEffect. Данный "оберточный" интерфейс инкапсулирует в себе следующие особенности:
  • Содержат глобальные переменные, которые можно устанавливать из приложения;
  • Позволяют манипулировать (управлять) состоянием механизма воспроизведения;
  • Управляют текстурными состояниями и состояниями семплеров (определяют файлы текстур, инициализируют текстурные уровни и их настройки);
  • Управляют механизмом визуализации с помощью шейдеров;

  • Рассмотрим пример использования файлов эффектов, написанных на языке HLSL. Ниже представлен простейший пример файла эффектов.
    float4x4 WorldViewProj; float4x4 World; float4 Light;
    texture Tex0 < string name = "texture.bmp"; >;
    struct VS_INPUT { float4 position : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; };
    struct VS_OUTPUT { float4 position : POSITION; float4 color : COLOR0; float2 texcoord : TEXCOORD0; };
    VS_OUTPUT main_vs( VS_INPUT In ) { VS_OUTPUT Out; Out.position = mul( In.position, WorldViewProj ); float3 pos = mul( In.position, World ); float3 light = normalize(vecLight-pos); float3 normal = normalize(mul( In.normal, World )); float4 green = {0.0f, 1.0f, 0.0f, 1.0f}; Out.texcoord = In.texcoord; Out.color = green*dot(light, normal); return Out; }
    sampler Sampler = sampler_state { Texture = (Tex0); MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR; };
    struct PS_INPUT { float2 texcoord : TEXCOORD0; float4 color : COLOR0; };
    float4 main_ps( PS_INPUT In ) : COLOR0 { return tex2D(Sampler, In.texcoord ) * In.color; }
    technique tec0 { pass p0 { VertexShader = compile vs_1_1 main_vs(); PixelShader = compile ps_1_1 main_ps(); } }
    Пример 6.2.
    Как видно из представленного примера, файлы эффектов объединяют в себе вершинный, пиксельный шейдеры, а также различные настройки режима воспроизведения.
    Кроме того, файлы эффектов позволяют объединить ряд вариантов воспроизведения в одном файле. Такая модульность открывает широкие возможности по использованию одной программы на различных аппаратных решениях (компьютерах с различными техническими возможностями). Каждый файл эффектов может содержать один и более разделов technique, которые как раз и предназначены для различных способов реализации алгоритма воспроизведения. Каждый раздел techniques может содержать внутри себя один и более разделов rendering pass, которые объединяют в себе состояния устройства вывода, текстурные уровни, шейдеры. Следует заметить, что файлы эффектов не ограничены в использовании только программируемых элементов графического конвейера (шейдеров). Они также могут использоваться и в фиксированном конвейере для управления состояниями устройства вывода, источниками света, материала ми и текстурами. Применение нескольких разделов rendering pass позволяют получить различные эффекты визуализации, например, двухпроходный алгоритм построения тени с использованием буфера трафарета. Ниже представлен шаблон файла эффектов, в котором присутствует два раздела technique, плюс к этому второй раздел содержит два подраздела rendering pass.

    technique tec0 { pass p0 // первый и единственный раздел rendering pass { // состояние устройства вывода, шейдеры, семплеры } }

    technique tec1 { pass p0 // первый rendering pass { // состояние устройства вывода, шейдеры, семплеры }

    pass p1 // второй раздел rendering pass { // состояние устройства вывода, шейдеры, семплеры } }

    Для управления файлами эффектов из приложения необходимо воспользоваться методами интерфейса ID3DXEffect. Вначале необходимо объявить переменную интерфейсного типа ID3DXEffect, и возможно переменную для буфера ошибок.

    C++

    LPD3DXEFFECT Effect = NULL; LPD3DXBUFFER BufferErrors = NULL;
    Pascal

    var Effect: ID3DXEffect; BufferErrors: ID3DXBuffer;
    Для загрузки и компиляции файла эффектов предназначена функция D3DXCreateEffectFromFile(), которая имеет следующие параметры:


  • Ссылка на устройство вывода;
  • Имя файла эффекта;
  • Набор макро определений (может быть пустым);
  • Указатель на интерфейс ID3DXInclude для обработки включений в файле эффекта (может быть пустым);
  • Набор флагов компиляции (может быть нулем);
  • Указатель на интерфейс ID3DXEffectPool для обработки общих параметров в эффекте (может быть пустым);
  • Указатель на полученный результат;
  • Указатель на буфер ошибок.
  • C++

    D3DXCreateEffectFromFile( device, "effect.fx", NULL, NULL, 0, NULL, &Effect, &BufferErrors)
    Pascal

    D3DXCreateEffectFromFile(device, ' effect.fx ', nil, nil, 0, nil, Effect, BufferErrors);
    Следующим шагом является установка активного раздела technique. Это реализуется с помощью вызова метода SetTechnique интерфейса ID3DXEffect, где в качестве параметра передается название раздела technique.

    C++Effect->SetTechnique("tec0");
    PascalEffect.SetTechnique('tec0');
    Затем необходимо определить количество рендер проходов (разделов rendering pass), которые будут выполняться. Делается это при помощи вызова метода Begin интерфейса ID3DXEffect, который имеет два параметра: первый параметр – возвращаемое значение количества разделов pass и второй параметр – флаг, указывающий на необходимость сохранения состояния режимов воспроизведения (значение ноль). И заключительный шаг состоит в организации цикла по всех разделам rendering pass с активацией текущего на каждом шаге. На каждой итерации вызывается необходимый код вывода примитива.

    C++

    unsigned int numPasses = 0;

    Effect->Begin(&numPasses, 0); for(unsigned int pass = 0; pass < numPasses; pass++) { Effect->BeginPass(pass); device->DrawPrimitive(...); Effect->EndPass(); } Effect->End();
    Pascal

    var numPasses: DWord; pass: DWord; ... Effect._Begin(@numPasses, 0); for pass:=0 to numPasses-1 do begin Effect.BeginPass(pass); Device.DrawPrimitive(...); Effect.EndPass; end; Effect._End;
    Также как и в программах, написанных на языке HLSL, в файлы эффектов можно передавать параметры. Установка значений параметров осуществляется через вызов методов SetXXX интерфейса ID3DXEffect.Ниже представлены основные методы передачи параметров в файл эффектов:

    SetBool() SetInt() SetMatrix() SetString() SetTexture() SetVector()

    Следует отметить еще одно достоинство файлов эффектов. Для работы с ними существует программа EffectEdit, которая поставляет вместе с DirectX SDK. Эта программа позволяет загружать файлы эффектов и осуществлять рендеринг сцены при установленных настройках графического конвейера (шейдеров, текстур, состоянийустройства воспроизведения и др.). Кроме того, утилита EffectEdit может быть использована в качестве отладочного механизма ваших шейдерных программ.

    Обработка ошибок

    При использовании графической библиотеки Direct3D программисту следует уделять особе внимание обработке ошибок. Для этих целей предусмотрены два специальных макроса, которые проверяют код на наличие или отсутствие ошибок:
    FAILED() SUCCEEDED()
    Принцип работы этих макросов очень прост и представлен ниже.
    if ( FAILED()) // ошибка при выполнении команды ... else // команда завершилась успешно
    Для второго макроса принцип действия будет противоположным.
    if (SUCCEEDED ()) // команда завершилась успешно ... else // ошибка при выполнении команды
    Ниже приведены некоторые примеры обработки ошибок.
    C++
    LPDIRECT3D9 direct3d = NULL; ... if( FAILED( direct3d -> CreateDevice( … ) ) ) { // обработка ошибки }
    Pascal
    var direct3d: IDirect3D9; ... if Failed ( direct3d.CreateDevice( … ) ) then begin // обработка ошибки end;

    Можно осуществлять анализ ошибок через дополнительную переменную.
    C++
    HRESULT hr; hr = direct3d -> CreateDevice( … ); if( FAILED(hr) ) {
    }
    Pascal
    var hr: HRESULT; … hr := direct3d.CreateDevice( … ); if Failed(hr) then begin
    end

    И еще один способ обработки ошибок заключается в сравнении результата вызова функции с константой D3D_OK.
    C++
    if ( direct3d -> CreateDevice( … ) != D3D_OK ) {
    }
    Pascal
    if direct3d.CreateDevice( … ) <> D3D_OK then begin
    end;

    Следует также остановиться на обработке ошибок при компиляции шейдеров. Как мы помним, в одном из параметров функции D3DXCompileShaderFromFile(…) возвращается указатель на буфер ошибок. Ниже приводится программный код, позволяющий вывести на экран сообщение об ошибке, полученное от компилятора HLSL.
    C++
    if ( D3DXCompileShaderFromFile( "shader.psh", NULL, NULL, "main", "ps_2_0", 0, &Code, &BufferErrors, NULL) != D3D_OK ) { MessageBox(NULL, (const char*)BufferErrors->GetBufferPointer(), "Ошибка в шейдере!!!", MB_OK | MB_ICONEXCLAMATION); }
    Pascal
    if D3DXCompileShaderFromFile('shader.psh', nil, nil, 'Main','ps_2_0', 0, @Code, @BufferErrors, nil) D3D_OK then begin MessageBox(0, BufferErrors.GetBufferPointer, 'Ошибка в шейдере!!!', MB_OK or MB_ICONEXCLAMATION); end;

    Такой информативный способ позволяет легко "увидеть" в какой именно строке шейдера допущена ошибка.

    Подсчет количества кадров в секунду

    Как правило, показателем производительности в приложениях, связанных с трехмерной графикой, является количество кадров, выводимых приложением в единицу времени (в секунду). Поэтому очень часто возникает необходимость вычислить и отобразить эту информацию в окне приложения. Ниже приведен пример, показывающий как подсчитывать количество кадров в секунду.
    C++
    count = 0; last_tick = GetTickCount(); while(msg.message != WM_QUIT) { if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(Msg); DispatchMessage(Msg); } else { this_tick = GetTickCount(); if(this_tick-last_tick>=1000) { last_tick=this_tick; fps=count; count=0; // вывод значения fps } else count++ Render(); } }
    Pascal
    count:=0; last_tick:=GetTickCount(); while Msg.message <> WM_QUIT do begin if PeekMessage(msg, 0, 0, 0, PM_REMOVE) then begin TranslateMessage(Msg); DispatchMessage(Msg); end else begin this_tick:=GetTickCount(); if(this_tick-last_tick>=500) then begin last_tick:=this_tick; fps:=count; count:=0; // вывод значения fps end else count:=count+1; Render; end; end;

    Подсчет количества кадров в секунду

    Полноэкранный режим воспроизведения

    До сих пор вывод всех сцен построения производился в окно (на форму). Библиотека Direct3D позволяет осуществить рендеринг в полноэкранном режиме (Full Screen). Для перехода в полноэкранный режим вывода примитивов необходимо лишь при заполнении структуры D3DPRESENT_PARAMETERS явно указать размеры заднего буфера (BackBuffer), установить флаг Windowed в значение FALSE. Также нужно указать режим переключения первичного и вторичного (заднего) буферов рендеринга и определить интервал смены буферов.
    C++
    D3DPRESENT_PARAMETERS params; … ZeroMemory( ¶ms, sizeof(params) ); params.Windowed = FALSE; params.SwapEffect = D3DSWAPEFFECT_FLIP; params.BackBufferWidth = 1280; params.BackBufferHeight = 1024; params.BackBufferFormat = display.Format; params.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; …
    Pascal
    var params: TD3DPresentParameters; … ZeroMemory( @params, SizeOf(params) ); params.Windowed := False; params.SwapEffect := D3DSWAPEFFECT_FLIP; params.BackBufferWidth := 1280; params.BackBufferHeight := 1024; params.BackBufferFormat := display.Format; params.PresentationInterval := D3DPRESENT_INTERVAL_IMMEDIATE; …

    Единственно, что необходимо еще проделать – это организовать возможность выхода (закрытия) программы. Для этого можно, например, обработать событие системы WM_KEYDOWN. Полноэкранный режим довольно часто используют в играх, так как именно при таком режиме достигается максимальная производительность видеокарты. Можно воспользоваться функцией Win32 API GetSystemMetrics() для того, чтобы узнать текущее разрешение экрана.
    C++
    D3DPRESENT_PARAMETERS params; … params.BackBufferWidth = GetSystemMetrics(SM_CXSCREEN); params.BackBufferHeight = GetSystemMetrics(SM_CYSCREEN);
    Pascal
    var params: TD3DPresentParameters; … params.BackBufferWidth := GetSystemMetrics(SM_CXSCREEN); params.BackBufferHeight := GetSystemMetrics(SM_CYSCREEN);


    float4x4 WorldViewProj; float4x4 World; float4

    float4x4 WorldViewProj; float4x4 World; float4 Light;
    texture Tex0 < string name = "texture.bmp"; >;
    struct VS_INPUT { float4 position : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; };
    struct VS_OUTPUT { float4 position : POSITION; float4 color : COLOR0; float2 texcoord : TEXCOORD0; };
    VS_OUTPUT main_vs( VS_INPUT In ) { VS_OUTPUT Out; Out.position = mul( In.position, WorldViewProj ); float3 pos = mul( In.position, World ); float3 light = normalize(vecLight-pos); float3 normal = normalize(mul( In.normal, World )); float4 green = {0.0f, 1.0f, 0.0f, 1.0f}; Out.texcoord = In.texcoord; Out.color = green*dot(light, normal); return Out; }
    sampler Sampler = sampler_state { Texture = (Tex0); MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR; };
    struct PS_INPUT { float2 texcoord : TEXCOORD0; float4 color : COLOR0; };
    float4 main_ps( PS_INPUT In ) : COLOR0 { return tex2D(Sampler, In.texcoord ) * In.color; }
    technique tec0 { pass p0 { VertexShader = compile vs_1_1 main_vs(); PixelShader = compile ps_1_1 main_ps(); } }
    Пример 6.2.
    Закрыть окно




    float4x4 WorldViewProj;
    float4x4 World;
    float4 Light;
    texture Tex0 < string name = "texture.bmp"; >;
    struct VS_INPUT
    {
    float4 position : POSITION;
    float3 normal : NORMAL;
    float2 texcoord : TEXCOORD0;
    };
    struct VS_OUTPUT
    {
    float4 position : POSITION;
    float4 color : COLOR0;
    float2 texcoord : TEXCOORD0;
    };
    VS_OUTPUT main_vs( VS_INPUT In )
    {
    VS_OUTPUT Out;
    Out.position = mul( In.position, WorldViewProj );
    float3 pos = mul( In.position, World );
    float3 light = normalize(vecLight-pos);
    float3 normal = normalize(mul( In.normal, World ));
    float4 green = {0.0f, 1.0f, 0.0f, 1.0f};
    Out.texcoord = In.texcoord;
    Out.color = green*dot(light, normal);
    return Out;
    }
    sampler Sampler = sampler_state
    {
    Texture = (Tex0);
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    };
    struct PS_INPUT
    {
    float2 texcoord : TEXCOORD0;
    float4 color : COLOR0;
    };
    float4 main_ps( PS_INPUT In ) : COLOR0
    {
    return tex2D(Sampler, In.texcoord ) * In.color;
    }
    technique tec0
    {
    pass p0
    {
    VertexShader = compile vs_1_1 main_vs();
    PixelShader = compile ps_1_1 main_ps();
    }
    }

    Расчет освещенности с помощью шейдеров

    Рассмотрим теперь более подробно, как осуществляется расчет освещенности грани в библиотеке Direct3D. При закраске поверхностей объекта в идеале мы должны для каждой точки грани (полигона) вычислять интенсивность освещения методами, описанными выше. Во многих графических библиотеках, в том числе и в Direct3D прибегают к приближенным методам закраски граней трехмерного объекта, состоящего из полигонов в силу того, что процесс вычисления значения интенсивности для каждого пикселя, является вычислительно трудоемким. В компьютерной графике используют, как правило, три основных способа закраски полигональной сетки: однотонная закраска, закраска, основанная на интерполяции значений интенсивности, и закраска, построенная на основе интерполяции векторов нормали. Библиотека Direct3D располагает способами использования только двух первых методов.
    При однотонной закраске вычисляется один уровень интенсивности, который используется для закраски всего полигона. При использовании закраски этого типа будет проявляться эффект резкого перепада интенсивности на всех граничных ребрах объекта.
    Метод закраски, который основан на интерполяции интенсивности (метод Гуро), позволяет устранить дискретность изменения интенсивности. Процесс закраски по методу Гуро осуществляется следующими шагами:
  • определяется (вычисляется или изначально задается) нормаль к каждой вершине полигона; причем для различных полигонов, имеющих общую вершину, нормали, как правило, различаются;
  • вычисляются значения интенсивности в каждой вершине полигона, используя рассмотренные выше модели освещенности;
  • полигон закрашивается путем линейной интерполяции значений интенсивностей в вершинах сначала вдоль каждого ребра, а затем и между ребрами вдоль каждой сканирующей строки

  • В некоторых экзотических случаях и метод закраски Гуро не позволяет полностью устранить перепады интенсивности. В этом случае можно воспользоваться закраской по методу Фонга. Алгоритмически метод Фонга схож с методом Гуро. Но в отличие от интерполяции значений интенсивности в методе Гуро, в методе Фонга используется интерполяция векторов нормали вдоль сканирующей строки.
    Функция mul() производит умножение вектора на матрицу, функция normalize() осуществляет нормировку вектора, функция dot() вычисляет скалярное произведение двух векторов. Как видно из представленных строк кода, для каждой вершины мы должны определять вектор на источник света и преобразовывать нормаль, как показано ниже.

    Расчет освещенности с помощью шейдеров

    Таким образом, каждая вершина трехмерной модели получит цвет в соответствие с ее нормалью и вектором на источник. После выполнения этих шагов все вершины передаются на этап компоновки примитивов и растеризации. Растеризатор делит каждый треугольник на элементарные фрагменты (пиксели) и затем производит линейную интерполяцию текстурных координат и цвета.

    Такой способ обработки дает возможность реализовать попиксельное освещение граней объекта. Реализовать это можно следующим образом. В силу того, что все атрибуты вершины (положение, цвет, текстурные координаты и т.д.), вычисленные в вершинном шейдере, интерполируются "по треугольнику", то можно, например, в качестве текстурных координат передать значения нормали вершины. Таким образом, графический конвейер воспримет нормаль как текстурные координаты вершины, и для каждого растеризуемого пикселя произведет интерполяцию векторов нормали. Входными данными для вершинного шейдера будут координаты вершины и нормаль.

    struct VS_INPUT { float4 position : POSITION; float3 normal : NORMAL; };

    Выходные данные вершинного шейдера мы описываем уже тройкой атрибутов: преобразованная вершина, преобразованная нормаль и вектор на источник света.

    struct VS_OUTPUT { float4 position : POSITION; float3 light : TEXCOORD0; float3 normal : TEXCOORD1; };

    Следует заметить, что атрибуты вершины нормаль и вектор на источник определены как текстурные координаты размерности 3 (float3). Тело вершинного шейдера будет выглядеть следующим образом.

    VS_OUTPUT main_vs( VS_INPUT In ) { VS_OUTPUT Out; Out.position = mul( In.position, WorldViewProj ); float3 pos = mul( In.position, World ); Out.light = normalize(vecLight-pos); Out.normal = normalize(mul( In.normal, World )); return Out; }


    Глобальные переменные будут такими же как и в предыдущем случае.

    float4x4 WorldViewProj; float4x4 World; float4 vecLight;

    Таким образом, пиксельный шейдер будет приминать на вход всего два вершинных атрибута: нормаль и вектор на источник. Сама же процедура пиксельного шейдера будет выглядеть так.

    float4 main_ps(float3 light : TEXCOORD0, float3 normal : TEXCOORD1) : COLOR0 { float4 diffuse = {1.0f, 1.0f, 0.0f, 1.0f}; return diffuse*dot(light, normal); }

    Ниже приведены примеры закраски по методу Гуро (слева) и Фонга (справа).

    Расчет освещенности с помощью шейдеров

    Рассмотрим еще один очень распространенный эффект построения реалистичных изображений, называемый bump-mapping или микрорельефное текстурирование, причем без существенных вычислительных затрат. Идея этого подхода заключается в моделировании рельефной поверхности с помощью двух текстур. Одна из них представляет собой изображение некоторой поверхности, а другая – так называемая карта нормалей. Карта нормалей представляет собой текстуру, где каждый пиксель является вектором нормали. Можно сказать, что карта нормалей несет в себе информацию о неровностях в каждом пикселе изображения. Как известно интенсивность освещения зависит от угла между нормалью в точке и вектором на источник света (закон косинусов Ламберта).

    Расчет освещенности с помощью шейдеров

    В зависимости от вектора нормали интенсивность в каждом пикселе будет различной. Следует заметить, что вектор нормали (nx, ny, nz) и цвет (R, G, B) кодируется тройкой чисел. Но цвет кодируется тремя положительными величинами из отрезка [0, 1], тогда как компоненты вектора нормали могут принимать и отрицательные значения. Так как координаты нормализованного вектора лежат в диапазоне [-1, 1], то можно с помощью линейного преобразования отобразить отрезок [-1, 1] в отрезок [0, 1]. Для этого можно воспользоваться следующей формулой: N*0.5+0.5, где N –вектор нормали. Такой процесс кодировки проделывается для каждого компонента вектора. Обратное преобразование (отрезок [0, 1] отобразить в отрезок [-1, 1]) может быть реализовано с помощью формулы 2*C–1, где C – значение цвета.


    Например, вектор (1, 0, 1) будет преобразован в тройку чисел (1, 0.5, 1) – светло- фиолетовый цвет. Для получения карты нормалей из исходного изображения имеются специальные программы и алгоритмы, например, существует плагин к PhotoShop’у, с помощью которого можно получить карту нормалей. Ниже представлен пример текстуры и соответствующая ей карта нормалей.



    Расчет освещенности с помощью шейдеров


    Расчет освещенности с помощью шейдеров
    Исходное изображениеКарта нормалей
    Таким образом, для вычисления интенсивности в каждом пикселе необходимо:

  • Получить цвет из исходной текстуры;
  • Получить закодированное значение вектора нормали из карты нормалей;
  • Произвести преобразование значений из цветового пространства в пространство нормалей;
  • Вычислить скалярное произведение нормали на вектор источника света;
  • Умножить полученное значение на цвет исходной текстуры.


  • Пиксельный шейдер, реализующий данные шаги показан ниже.

    float4 Light; sampler tex0; sampler tex1;

    struct PS_INPUT { float2 uv0 : TEXCOORD0; float2 uv1 : TEXCOORD1; float4 color: COLOR0; };

    float4 Main (PS_INPUT input): COLOR0 { float4 texel0 = tex2D(tex0, input.uv0); float4 texel1 = 2.0f*tex2D(tex1, input.uv1) - 1.0f; return texel0*dot(normalize(Light), texel1); };

    Значение положения источника света передается в пиксельный шейдер через переменную Light. Ниже показаны примеры микротекстурирования при различных положениях источника света.



    Расчет освещенности с помощью шейдеров


    Расчет освещенности с помощью шейдеров
    return texel0*dot(normalize(Light), texel1);


    Расчет освещенности с помощью шейдеров


    Расчет освещенности с помощью шейдеров
    return input.color*texel0*dot(normalize(Light), texel1);

    Дополнительные материалы

    Ниже приводятся типовые варианты лабораторных работ.
    Лабораторная работа № 1
    Разработать программу, обеспечивающую вывод на форму большого числа (более 10000) плоских геометрических примитивов (точек, отрезков, треугольников) с использованием библиотеки Direct3D и определить количество выводимых кадров в секунду для каждого типа примитивов. Выходные данные программы (результаты) оформить в виде таблицы следующего вида:
    Тип примитиваКоличествоКоличество кадров в секунду (fps)
    Несвязные отрезки1000040

    Лабораторная работа № 2
    Разработать программу, обеспечивающую визуализацию прямоугольников произвольного размера с наложенной текстурой и их вращение вокруг собственного центра масс. Размеры прямоугольников и текстур должны быть степенью двойки. Результаты оформить в виде таблицы:
    Количество прямоугольниковРазмер накладываемой текстурыКоличество кадров в секунду (fps)
    2000512х51225

    Лабораторная работа № 3
    Разработать программу, реализующую вращение текстурированного выпуклого полупрозрачного объекта. В качестве результата вывести количество кадров в секунду.
    Лабораторная работа № 4
    Разработать программу, обеспечивающую визуализацию трехмерного объекта произвольной сложности, освещенного точечными источниками, и отбрасывающего тень на плоскость y=0. Результаты оформить в виде таблицы:
    Количество полигонов в объектеКоличество источников светаКоличество кадров в секунду (fps)
    3000320

    Лабораторная работа № 5
    Разработать программу, реализующую преобразование трехмерного объекта при помощи эффекта скручивания (blending vertex). Вершинный шейдер должен быть написан на языке HLSL. В качестве результата вывести количество кадров в секунду.
    Лабораторная работа № 6
    Разработать программу, обеспечивающую попиксельную закраску трехмерного объекта методами Гуро и Фонга. Пиксельный шейдер должен быть написан на языке HLSL. В качестве результата вывести количество кадров в секунду.
    Лабораторная работа № 7
    Разработать программу с использованием пиксельного шейдера, обеспечивающую обработку изображений точечными и пространственными процессами.
    Пиксельные шейдеры должны быть написаны на языке HLSL. Результат оформить в виде таблицы.

    Размер изображенияТип обработкиКоличество миллисекунд
    512х512фильтр Собеля20
    Лабораторная работа № 8

    Разработать программу обработки изображений точечными и пространственными процессами с использованием центрального процессора (CPU). Результат оформить в виде таблицы

    Размер изображенияТип обработкиКоличество миллисекунд
    512х512фильтр Собеля1000
    и сравнить с предыдущими. Дополнительные материалы 

        Программирование: Языки - Технологии - Разработка