Софтварный конвеер рендеринга - минимальные требования

Сначала необходимо создать пустой конскольный проект в Visual Studio, затем добавить в проект пустой файл .cpp, и в него скопировать текст примера ниже.

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

Трехмерная модель может быть создана например в пакете трехмерного моделирования 3DS Max. В данной статье, в программе 3DS Max была создана модель трехмерно куба, модель была сохранена в файл, и затем из этого файла были взяты координаты куба xyz, которые были помещены в массив вершин в коде С++, в данном примере.

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

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

Минимальный ковеер рендеринга выглядит так:
1 - имеется трехмерная модель куба, все 24 вершины куба записаны в массиве в формате xyz;
2 - модель необходимо разместить на сцене, повернуть, или поменять масштаб модели. для этого каждую вершину трехмерной модели куба xyz умножить на матрицы мира, вида, проекции, т.е. умножить на матрицы трансформации, например матрица поворота по оси Y, матрица перемещения модели на сцене, или матрица мира (т.е. матрица размещения на сцене), фактически необходимо выполнить операцию умножения вектора на матрицу;
3 - поскольку ширина и высота экрана монитора (или ширина и высота окна приложения) имеют разные значения, мы должны вывести изображение без потерь пропорции; каждую координату x из всех вершин модели необходимо поделить на значение величины пропорций экрана. Пропорции экрана это - ширина экрана поделенная на высоту.
4 - далее необходимо создать иллюзию перспективы на экране монитора- то есть объекты которые ближе, выглядят больше, объекты которые далшье на сцене- должны выглядеть меньше; каждую вершину модели- значения x и y поделить на значение z этой же вершины.
5 - теперь над всеми 24 вершинами модели следует произвести преобразование в экранные координаты, в зависимости от разрешения дисплея, можно использовать экранную матрицу; после деления на z в предыдущем пункте, мы получим вершины в диапазоне от 0 до 1, теперь эти знаения вершин от 0 до 1 нужно привести в соответствие к размерам экрана, например 800 на 600 пикселей; т.е. например 0 это 0, а 1 это 800, и 0 это 0 а 1 это 600; в результате мы получим, что вершина со значением х = 0,25 будет равна в экранных координатах 200 пикселей; вершина со значением y = 0,5 будет равна 300 пикселей;
6 - теперь когда есть экранные координаты модели, используя графические средства- провести линии между рассчитанными 24 точками на экране, в результате мы получим проволочное изображение иходной модели на экране, т.е. каркасную модель куба;


Файл .cpp примера Загрузить





Код примера на С++.


//подключаем файлы заголовков

#include <windows.h>
#include <math.h>

//глобальная переменная дескриптор окна приложения
//будет использоватся для создании конетекста устройства
//при рисовании линий каркасной модели куба

HWND hwnd;

//структура будет содержать все 24 вершины xyz модели куба

typedef struct slib_vector3
{
float x,y,z;
} lib_vector3;

//структура будет содержать матрицы трансформации

typedef struct slib_4x4_matrix
{
float m[4][4];
} lib_4x4_matrix;

//функция умножает каждую вершину модели куба xyz на матрицу 4x4
//фактически в этой функции производится математическая операция
//умножения вектора из трех компонент xyz на матрицу размером 4x4

lib_vector3 Mat4x4_Vect3_Mul( lib_4x4_matrix mat, lib_vector3 v )
{
lib_vector3 temp;

temp.x = v.x * mat.m[0][0] + v.y * mat.m[1][0] + v.z * mat.m[2][0] + mat.m[3][0];
temp.y = v.x * mat.m[0][1] + v.y * mat.m[1][1] + v.z * mat.m[2][1] + mat.m[3][1];
temp.z = v.x * mat.m[0][2] + v.y * mat.m[1][2] + v.z * mat.m[2][2] + mat.m[3][2];

return temp;
}

//главная функция приложения, которая производит все вычисления
//и выводит каркасное изображение модели куба на экран в каждом кадре

void Draw_Cube()
{

//куб на экране будет вращатся, вычисляем угол на который модель куба
//будем поворачивать в каждом кадре

static float angle = 0.0;

//в каждом кадре к значению угла поворота добавляем новое значение

angle+=(float)(3.1415926/180);

//если угол поворота болье 360 градусов делаем его равным 0

if(angle>3.1415926*2) angle = 0;

//модель куба содержит 24 вершины

UINT nVertCount = 24;

//для дальнейших преобразований
//заносим в массив все 24 вершины модели куба

lib_vector3 vert_buff[24] = {-5.000000, 5.000000,-5.000000,
5.000000, 5.000000,-5.000000,
5.000000, -5.000000,-5.000000,
-5.000000, -5.000000,-5.000000,
-5.000000, 5.000000, 5.000000,
5.000000, 5.000000, 5.000000,
5.000000, 5.000000,-5.000000,
-5.000000, 5.000000,-5.000000,
-5.000000, -5.000000,5.000000,
5.000000, -5.000000,5.000000,
5.000000, 5.000000, 5.000000,
-5.000000, 5.000000, 5.000000,
-5.000000, -5.000000,-5.000000,
5.000000, -5.000000,-5.000000,
5.000000, -5.000000, 5.000000,
-5.000000, -5.000000, 5.000000,
-5.000000, 5.000000, 5.000000,
-5.000000, 5.000000,-5.000000,
-5.000000,-5.000000,-5.000000,
-5.000000,-5.000000, 5.000000,
5.000000, 5.000000,-5.000000,
5.000000, 5.000000, 5.000000,
5.000000,-5.000000, 5.000000,
5.000000,-5.000000,-5.000000};


//объявляем матрицу вращения по оси X
//используя значение угла поворота

lib_4x4_matrix mWorldRotX={
1, 0, 0, 0,
0, cos(angle), sin(angle), 0,
0,-sin(angle), cos(angle), 0,
0, 0, 0, 1};

//объявляем матрицу вращения по оси Y
//используя значение угла поворота

lib_4x4_matrix mWorldRotY={
cos(angle), 0, -sin(angle), 0,
0, 1, 0, 0,
sin(angle), 0, cos(angle), 0,
0, 0, 0, 1};

//объявляем матрицу вращения по оси Z
//используя значение угла поворота

lib_4x4_matrix mWorldRotZ={
cos(angle), sin(angle), 0, 0,
-sin(angle), cos(angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1};

//матрица мира, размещает куб на сцене
//на расстоянии 15 единиц в глубину от зрителя

lib_4x4_matrix mWorld={
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 15.0, 1};




//получаем размеры окна приложения

RECT rc;
GetClientRect(hwnd, &rc);

long nViewWidth = rc.right - rc.left;
long nViewHeight = rc.bottom - rc.top;

long nViewX = rc.left;
long nViewY = rc.top;


//вычисляем apect ratio, соотношение ширины окна приложения к его высоте

float fAspect = (float) nViewWidth/nViewHeight;


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

for ( UINT i = 0; i < nVertCount; i++)
{
//умножаем каждую вершину модели куба на матрицу вращения по оси X, по оси Y
//и на матрицу мира (матрицу вращения по оси Z не используем, она была
//приведена выше в ознакомительных целях)

vert_buff[i] = Mat4x4_Vect3_Mul(mWorldRotX, vert_buff[i]);
vert_buff[i] = Mat4x4_Vect3_Mul(mWorldRotY, vert_buff[i]);
vert_buff[i] = Mat4x4_Vect3_Mul(mWorld, vert_buff[i]);

//учитываем пропорции экрана, координата x делится
//на значение aspect ratio (соотношение сторон окна приложения)

vert_buff[i].x = vert_buff[i].x / fAspect;

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

vert_buff[i].x = vert_buff[i].x / vert_buff[i].z;
vert_buff[i].y = vert_buff[i].y / vert_buff[i].z;

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

vert_buff[i].x = vert_buff[i].x * nViewWidth / 2 + nViewX + nViewWidth / 2;
vert_buff[i].y = -vert_buff[i].y * nViewHeight / 2 + nViewY + nViewHeight / 2;
}


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

HDC hDC = ::GetDC( hwnd );

LOGBRUSH logBrush;
HBRUSH hBrush = NULL;
logBrush.lbStyle = BS_SOLID;
logBrush.lbColor = 0 ;
hBrush = ::CreateBrushIndirect( &logBrush );
FillRect(hDC,&rc,hBrush);

//в цикле проходим во всем 6- ти сторонам куба
//соединяя линиями все 4- ре вершины каждой стороны

for ( int i = 0; i < 6; i++)
{

//создаем перо средствами GDI

HPEN hPen = CreatePen(PS_SOLID, 5, RGB(128, 0, 0));
HPEN hOldPen = (HPEN)SelectObject (hDC, hPen);

//отмечаем начало первой линии

MoveToEx(hDC,(int)vert_buff[i * 4].x, (int)vert_buff[i * 4].y,NULL);

//соединяем линиями все 4- ре точки каждой стороны модели куба

LineTo(hDC,(int)vert_buff[i * 4 + 1].x, (int)vert_buff[i * 4 + 1].y);
LineTo(hDC,(int)vert_buff[i * 4 + 2].x, (int)vert_buff[i * 4 + 2].y);
LineTo(hDC,(int)vert_buff[i * 4 + 3].x, (int)vert_buff[i * 4 + 3].y);
LineTo(hDC,(int)vert_buff[i * 4].x, (int)vert_buff[i * 4].y);

//возвращаем старое перо в конекст устройства

SelectObject(hDC,hOldPen);
DeleteObject (hPen) ;
}


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

}



//функция main приложения

int main (int argc, char* argv[])
{

//устанавливаем заголовок в окне приложения название приложения
//находим дескриптор hwnd окна приложения для создания контекста
//вывода графики и рисования линий каркасной модели куба

char str[256];
SetConsoleTitle("Sowtware rendering engine (no OpenGL/DirectX)");
GetConsoleTitle(str, 256);
hwnd = FindWindow(NULL, str);

//создаем цикл обработки сообщений, в этот цикл добавляем
//функцию рисования модели куба, для плавного вращения куба
//ставим паузу 15 милисекунд, после каждой отрисовки
//модели куба, при получении соответствующего сообщения
//приложение завершает работу

MSG msg ;
while (TRUE)
{
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break ;

TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
else
{
Draw_Cube () ;
Sleep(15);
}
}


return 0;
}