Базовые концепции 3D графики на примере псевдокода

После того как мы рассмотрели два работающих примера- вращение модели куба вокруг оси X и Y, давайте рассмотрим немного теории- как устроена трехмерная графика.

Как работает 3D графика. 3D графика содержит три основных блока. 3 в слове 3D не связано с тем что сама графика содержит три основных блока :-), или ступени, перед тем как мы увидим готовую картинку на экране. Так что же происходит с самого начала, перед тем как зритель увидит уже готовую картинку на экране- может это будет экран компьютерной игры.

Первое есть трехмерная (объемная) модель. Второе- эта модель, все вершины модели, проходят обработку в так называемом конвеере рендеринга. Третье- мы видим в результате на двухмерном (плоском) экране картинку модели.

То есть обратите внимание на главную концепцию- объемная трехмерная модель, обработка, плоское двухмерное изображение на экране в итоге. Это изображение нам кажется 3D изображением, на самом деле это двухмерная картинка, которая отображает 3D сцену. 3D модель спроцирована на двухмерный экран монитора - так как монитор имеет только ширину и высоту XY, а 3D сцена имеет ширину, высоту и глубину XYZ.

То есть имееться ввиду что модель объемная, потому что она состоит из вершин, которые описываються тремя координатами X, Y, Z. А после обработки модель выводиться на плоский двухмерный экран компьютера, потому что координаты на экране опиываються двумя величинами X и Y.

Фактически нам надо объемную модель преобразовать в плоскую двухмерную картинку. Модель состоит из вершин. Поэтому модель проходит обработку путем математических операций с каждой ее вершиной. Конвеер рендеринга воздействует на вершины модели. Этим преобразованием и занимается конвеер рендеринга- от объемной трехмерной модели до двухмерной картинки на экране. Каждая вершина модели проходит математическую обработку, потом модель выводиться на экран. У куба 8м вершин, мы проводим математическую обработку каждой из 8ми вершин, затем соединяем их линиями- получаем проволочную модель куба. Почему объемная модель- потому что каждая вершина имеет координаты x, y, z. Например у куба (объемная модель) у куба 8 вершин, и каждая вершина содержит координаты x,y,z которые размещают эту вершину в трехмерном пространстве, есть три оси X, Y, Z – влево- вправо X, вверх-вниз Y, ближе- дальше это глубина Z. После обработки модели куба мы можем соединить эти вершины линиями MoveToEx() и LineTo()затем вывести куб на экран. Но если мы сделали модель куба (скажем в 3DS Max) и без обработки просто соединим вершины линиями- скорее всего на экране мы ничего не увидим. Эту модель куба, которую мы сделали в 3DS Max мы должны подвергнуть обработке. Как это выглядит в псевдокоде:

//массив содержит 8м вершин куба
vert_buffer[8];
//массив для обработанных вершин
vert_buff_transformed[8];

for ( i = 0; i < 8; i++)
{
//получаем следующую вершину из массива
vi = vert_buffer [i];

//обрабатываем вершину
vo = Calculate_Cube(vi);

//заносим обработанную вершину в массив
vert_buff_transformed [i] = vo;
}

//рисуем модель на экране
Draw_Cube();

Затем после выполнения этого кода мы можем соединить вершины линиями используя функции С++ GDI MoveToEx() и LineTo(). Соединять линиями нужно в правильной последовательности. Для этого существует массив индексов.

Что делает функция Calculate_Cube(vi). Она обрабатывает вершины куба. Давайте развернем функцию Calculate_Cube(vi) и напишем псевдокод обработки вершин куба по другому - с развернутой функцией.

for ( i = 0; i < 8; i++)
{
// первый шаг
v = vert_buffer [i];

// второй шаг
v.z = v.z + 15;

// третий шаг
v.x = v.x / v.z;
v.y = v.y / v.z;

// четвертый шаг
Aspect = ScreenWidth / ScreenHeight;
v.x = v.x / Aspect;

// пятый шаг
v.x =  v.x * ScreenWidth  / 2 + ScreenWidth / 2;
v.y = -v.y * ScreenHeight / 2 + ScreenHeight / 2;

//шестой шаг
vert_buffer_transformed[i] = v;
}

Первый шаг- получаем вершину в переменную.

Второй шаг-поскольку куб создан так что его центр в координатах (0, 0, 0) (т.е. локальные координаты модели и мировые координаты сцены совпладают) и после запуска программы зритель находиться в координатах (0, 0, 0) нам надо вперед отодвинуть куб, что бы зритель остался в координатах (0, 0, 0) а центер куба был размещен в поординатах (0, 0, z + 15) – то есть смотреть на куб со стороны, отодвинуть куб на +15 по оси Z в глубину. Поэтому к Z координате каждой вершины куба надо добавить 15 единиц, что бы отодвинуть куб вперед от наблюдателя. Как размещены оси в нашем случае. Влево идет минус X, вправо идет плюс X, вверх идет плюс Y вниз идет минус Y, к зрителю идет минус Z, от зрителя в глбунину сцены идет плюс Z. В третьем шаге мы отодвигаем куб от зрителя вперед на 15 единиц.

Третий шаг- и самый важный- нам надо создать проекцию. Что это значит- это самое главное свойство 3D графики и заключаеться оно в том что те объекты что находяться ближе- больше в размере тех, что находяться дальше в глубину экрана. Как достичь этого? Надо в четвертом шаге X и Y каждой вершины поделить на ее Z, и таким образом чем больше в глубину Z тем меньше X и Y чем и достигаеться свойство проецирования- ближе объекты больше, дальше- меньше в размерах. После деления на Z мы получаем NDC координаты вершины, т.е. normalized device coordinates - это координаты вершины когда все компоненты x,y,z находятся в пределах от -1.0 до 1.0. На самом деле в таких графических API как DirectX и OpenGL происходит деление на четвертую компоненту вектора W. Мы получаем четыре компоненты вектора (т.е. однородные координаты - homogeneous coordinates) после умножения вектора на матрицу проекции - об этом будет рассказано далее.

Пока наш маленький псевдокод не умеет размещать зрителя на сцене, и зритель остается в центе экрана (0, 0, 0). Например- мы можем не отодвигать куб на плюс 15 единиц в глубину по оси Z, оставить его в центре экрана, а отодвинуть зрителя скажем на минус 15 единиц по оси Z от модели куба. Таким образом центр куба будет в координатах (0, 0, 0) а зритель отодвинут на Z - 15. Когда модель создается в пакете моделирования, например 3DSMax, она создается в Локальных Координатах- все координаты лежат относительно центра модели. Для размещения модели на сцене есть матрица Мира, для размещения зрителя на сцене матрицы Вида, так же есть матрицы Проекции, и Экранная матрица. Эти матрицы из математики позволяют поворачивать, менять размер, и перемещать в 3D пространстве объекты, менять положение зрителя на сцене. Экранная матрица преобразовывает 3D координаты после обработки в координаты экрана. Так как центр нашей трехмерной системы координат находиться в центре экрана, но у самого экрана компьютера отсчет координат для вывода пикселей и линий начинаеться не в центре а в левом верхнем углу, нам надо преобразовать координаты в экранные. Как уже упоминалось, если не сдвигать куб на плюс 15 единиц в глубину по оси Z, то центр системы координат с вершинами куба размещен в центре куба, в центре экрана, то есть локальные координаты куба совпадают с мировыми. Влево- минус X, врпаво плюс X, вверх плюс Y, вниз минс Y, к зрителю минус Z, от зрителя плюс Z. Но сам вывод пикселей и линий при помощи функций MoveToEx() и LineTo() использует другие координаты- начало координат в левом верхнем углу, в экранных координатах начиная с левого верхнего угла экрана слево направо- плюс X, сверху вниз плюс Y.

Четвертый шаг- поскольку ширина экрана не совпадает с его высотой- нам нужно вычислить соотношение сторон экрана и умножит его на X каждой вершины, что бы сохранить пропорции, или по другому- откорректировать пропорции, иначе без второго шага куб на экране будет больше в ширину чем в высоту, так как размеры экрана по ширине и высоте не совпадают. Закомментируйте в программе эти строки вы увидите результат- куб будет больше в ширину чем в высоту. Ширина и высота экрана используются в пятом шаге, в этом шаге мы корректируем пропорции.

И в конце, пятый шаг- преобразование в экранные координаты- перед выводом на экран мы должны учитывать эту особенность что мы имеем трехмерную координатную систему изначально, и нужно координаты в этой системе координат преобразовать в экранные координаты. Где ScreenWidth и ScreenHeight это ширина и высота экрана. То есть центр (0, 0, 0) трехмерных осей из центра экрана мы смещаем в левый верхний угол что бы использовать функции MoveToEx() и LineTo() для отрисовки линий соединяющих вершины куба, если бы функции MoveToEx() и LineTo() могли рисовать отталкиваясь от центра экрана а не от его верхнего левого угла- то тогда этого преобразования в пятом шаге в экранные координаты было бы не надо. Так же в пятом шаге для этих преобразований можно умножать вершины на экранную матрицу - а не делать расчет экранный координат как было в примерах ранее, вместо расчета можно использовать экранную матрицу. Экранная матрица будет представлена в следующих главах.

Так же в пятом шаге, когда преобразовываем в экранные координаты, v.y береться со знаком минус, так как в трехмерной системе координат Y плюс направлен вверх, а в координатах экрана Y плюс направлен вниз, начиная с левого верхнего угла экрана.

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

После моделирования куба, например в 3DS Max, координаты всех 8ми вершин расположены начиная от центра куба. Это локальные координаты модели. Центр трехмерной системы координат сцены совпадает с центром куба- это локальные координаты модели куба. Но на трехмерной сцене к примеру вы не всегда модели располагаете в центре сцены, они имеют позицию на сцене. За позицию модели на сцене отвечает матрица Мира. За позицию на сцене наблюдателя отвечает матрица Вида. Зачем матрица проекции- он осудествляет корректировку вершины X из нашего третьего шага, эта корретировка нужна в связи с тем что ширина экрана больше его высоты. Так же у сцены есть ближняя плоскость отсечения и дальняя. А сама видимая область называеться пирамидой просмотра. Так же в матрице проекции координаты модели куба преобразовываються (накладываються) на тот диапазон значений где начинаеться ближняя плоскость и до задней плоскости отсечения. Координаты куба укладываються в этот промежуток- между ближней плоскостью отсечения и дальней. Кроме того в матрице проекции учитываеться уголо обзора FOV. Когда мы вершины модели умножаем на матрицу проекции то в результате мы получим картинку с тем полем обзора который мы выбрали в матрице проекции. Угол обзора береться в радианах. Например 3.1415926f/2.0f это угол обзора 90 градусов. Этими три пункта решает матрица проекции- первое- откорректировать X в связи с тем что ширина экрана больше высоты, второе - «упаковать» координаты модели в представление от ближней плоскости отсечения до дальней так как нам надо делить на Z создавая центральную проекцию, а на 0 делить нельзя, если модель у нас в начале координат, и третье- рассчитать показ модели на экране в соответствии с выбраным углом обзора FOV, который можно менять.

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

После того как модель построена из треугольников, а у треугольника 3 вершины, все треугольники проходят обработку в конвеере рендеринга, потом рисуються на двухмерном экране. В результате вывод на экран готовых вершин треугольников называеться растеризация. Мы можем провести линии которые соединяют вершины треугольников- в результате получим проволочную модель. Мы можем каждой вершине куба назначить цвет, и интерполировать три цвета каждой вершины между самими вершинами и рисовать попиксельно. Или мы можем интерполировать значения координат трех вершин треугольников (треугольник это базовый графический примитив) и взять значение текселя из текстуры- тогда мы увидим что на наш куб наложена текстура. Понятие пиксель употребляеться для указания позиции на экране монитора. А понятие тексель- для указания какого то отдельного элемента (точки) на изображении текстуры. Текстура может быть BMP или JPG или даже вы можете использовать свой формат который хранит цвета в файле, и вы потом его загружаете и накладываете как текстуру – главное из файла рисунка получить значения r,g,b. Одним словом, проволочная модель- это когда после обработки вершины соединяються линиями. Модель куба когда каждой вершине назначен ее цвет, здесь используеться интерполяция цветов между тремя вершинами каждого треугольника из которого состоит куб что бы получить плавный переход цвета от вершины к вершине. Модель куба на которую наложена текстура- тоже после обработки вершин у каждого треугольника между тремя вершинами используеться интерполяция текстурных координат что бы вывести тексель текстуры в определенном месте экрана, натести текстуру на куб.

Если построить куб из треугольников на него можно наложить текстуру. Как это реализуеться. Куб имеет 8м вершин. Куб имеет 6 сторон. Каждая сторона куба состоит из 2х треугольников. Каждый треугольник имеет 3 вершины. Всего 6 сторон куба- это 12 треугольников. 12 треугольников это 36 вершин. И на деле используеться буфер индексов. Что это такое- для куба буфер индексов может быть массив unsigned int из 36 элементов- соответственно для каждоый вершины. То есть мы имеем массив вершин- 8м вершин куба- они описывают как кажная вершина размещена в трехмерном пространстве. И есть массив интексов из 36 элементов- массив описывает как мы будем соединять в треугольники (в какой последовательности) эти вершины из первого массива. Первые три элеменат массива индексов- описывают первый треугольник- три вершины из массива вершин первого треугольника. Например первые три элемента массива интесoв 0, 2, 1 – это значит что из массива вершин для построения первого треугольника нужно взять последовательно вершину из массива элемент 0, затем элемент из массива вершин 2, и элемент из массива вершин 1. И это будет первый треугольник. И так в массив индексов заносяться по 3 вершины для всех 12 треугольников из которых состоит куб. Так же само как извлекаются вершины при помощи буфера индексов извлекаются из массива текстурные координаты. Размеры буфера вершин и буфера (массива) текстурных координат совпадают.