tanulas_3d_programozas - Fehér Krisztián honlapja

Fehér Krisztián weboldala
Fehér Krisztián weboldala
Tartalomhoz ugrás

tanulas_3d_programozas

Fehér Krisztián: 3d programozás

Tartalomjegyzék

1 Előszó
1.1 Miért saját rajzmotor?
1.2 A leírás célja és szerkezete
1.3 Miről szól a leírás ?
1.4 Miről NEM szól a leírás ?
1.5 Követelmények
1.6 Letölthető programkódok
2 Egy egyszerű 3D grafikus motor
2.1 Adatszerkezetek
2.2 Inicializálás, előkészületek
2.3 Forgatás és perspektivikus projekció
2.4 Mélységi rendezés
2.5 Zoomolás
2.6 Kirajzolás a képernyőre
3 3D modellek megjelenítése
3.1 Láthatósági tesztek
3.2 Textúrák használata
3.3 Fények alkalmazása
3.4 Teljesítménymérés, statisztikák
3.5 Záró gondolatok és könyvajánló
4 Függelék – teljes forráskód


1. Előszó

Gondolt már saját 3D grafikus motor elkészítésére?
Sokféle kész programozási segédeszköz létezik, de ezek tanulási görbéje nagyon nagy lehet és ezen eszközök csak korlátozott mértékű finomhangolást tesznek csak lehetővé.
Ennek a dilemmának a feloldása lehetséges egy saját 3D motor megírásával.
Amennyiben az olvasónak ez a célja, a megfelelő leírást tartja a kezében.

1.1 Miért saját rajzmotor?

Saját 3D motor megírásának számos előnye van:
• kicsi, jól átlátható kódbázis hozható létre
• megvan a pixelszintű finomhangolás lehetősége
• finomhangolható egy konkrét feladatra.

Mindazonáltal egy saját motor írásának számos nehézsége is van, például a megfelelő képletek és algoritmusok kidolgozása. Elsősorban ez az oka annak, amiért a legtöbben feladják egy saját 3D motor megírásának ötletét.
1.2 A leírás célja és szerkezete

A leírás elsődleges célja egy alapszintű 3D rajzolómotor bemutatása konkrét kódokkal, a különböző algoritmusokra koncentrálva.
Az itt bemutatott algoritmusok Direct2D API-t használnak a grafikus kimenet megjelenítéséhez, de a bemutatott adatszerkezetek és algoritmusok bármilyen grafikus programozási API-val felhasználhatóak.
Ebben a leírásban figyelmen kívül hagyjuk egy saját raszterizáló motor elkészítésének lehetőségét és a kimenetet a Direct2D beépített megjelenítési módszereivel készítjük el.
A leírás néhány példakódjának megalkotásakor sok inspirációt merítettem Dmitry V. Sokolov tinyrenderer leírásaiból: https://github.com/ssloy/tinyrenderer

1.3 Miről szól a leírás?

A leírás fő témái:
• a 3D programozás alapjai
• forgatási és vetítési algoritmusok
• takarás, láthatóság ellenőrzése
• alapvető megvilágítás
• nagyon alapvető texturázás
• egy alapvető OBJ modell-betöltő bemutatása
• kimeneti renderelés a Direct2D használatával.
1.4 Miről NEM szól a leírás?

Ez a leírás nem tér ki a következő témákra:
• magasszintű 3D modell formátumok kezelése
• bonyolult textúrázási megoldások
• modellek animációi
• tesszeláció
• shader programozás
• tükröződések
• árnyékolás
• sugárkövetés
• explicit raszterizáció
• GPU gyorsítás
• egy adott grafikus API (pl. Direct2D) bemutatása.

1.5 Követelmények

Némi tapasztalattal kell rendelkezni a Windows alkalmazások fejlesztésében C / C++ programozási nyelven.
A WIN32 API, a Direct2D és a Visual Studio mint fejlesztőeszközök alapvető ismerete erősen javasolt.
A leírás példakódjai klasszikus (WIN32) Windows desktop projekt keretében lettek kifejlesztve, Visual Studio Community Edition fejlesztőrendszerrel.
1.6 Letölthető programkódok

A leírásban található kódokat használó példaalkalmazás teljes forráskódja letölthető a következő URL-ről:




2. Egy egyszerű 3D grafikus motor

Egy grafikus motor minden olyan műveletet elvégez, melyek egy képkocka előállítását eredményezik. Leegyszerűsítve a dolgot: beöntünk a gépezetbe egy csomó bemeneti adatot és a végén kapunk egy képet a képernyőn.
A modern és fejlett grafikus API-k ezt lenyűgöző részletességgel és sebességgel képesek elvégezni, viszont ennek megfelelően borzasztóan bonyolultak is. Minket egy pehelysúlyú motor érdekel, amit jól át tudunk látni.
Tulajdonképpen 3 szakaszból fog állni a működése, a 4.-et pedig helyettesíthetjük bármilyen, számunkra kedves programozási nyelven elérhető grafikus metódussal, ill. függvénnyel. Ennek a leírásnak a példái a Direct2D-t használják.

A négy szakasz:
• vertex feldolgozás: csúcspontok beolvasása
• geometria előkészítése: minden vertexet háromszögek csúcspontjaiként tárolunk el a memóriában
• a csúcspontok elforgatása és vetítése a képsíkra
• renderelés: a kép/alakzatok kirajzolása.



2.1 Adatszerkezetek

A kimenet (képernyő) méreteit előre definiáljuk:


#define SCREEN_WIDTH 1920
#define SCREEN_HEIGHT 1000


Egy háromszög objektumot fogunk felhasználni a rajzolási parancsok előállításához:


D2D1_TRIANGLE triangle;


Ezt követően statikusan beállítjuk a csúcspontok maximális számát. Természetesen dinamikus memóriafoglalással még hatékonyabbak lehetünk, de ettől most eltekintünk.


#define MAX_OBJ_NUM 20000000


A következő változókat a forgatáshoz és vetítéshez használjuk:


int viewpoint = -1100;
float persp_degree, current_zoom;
float rot_degree_x;
float rot_degree_y;
float rot_degree_z;
float rot_degree_x2 = 0;
float rot_degree_y2 = 90.0f;
float rot_degree_z2 = 0;
float Math_PI = 3.14159265358979323846;


Az alábbi minimalista adatszerkezetek 3D csúcspontok és elforgatott változataik tárolására használhatóak:


float raw_verticesX[MAX_OBJ_NUM],
raw_verticesY[MAX_OBJ_NUM],
raw_verticesZ[MAX_OBJ_NUM];

float rotated_verticesX[MAX_OBJ_NUM],
rotated_verticesY[MAX_OBJ_NUM],
rotated_verticesZ[MAX_OBJ_NUM];

int raw_vertices_length;


A takarás észlelése érdekében néhány segéd adatstruktúra is kell (később részletesebben tárgyaljuk):


int zorder_length;
int zorder_index[MAX_OBJ_NUM];
float zorder_distance[MAX_OBJ_NUM];


2.2 Inicializálás, előkészületek

Mielőtt bármi történne, be kell állítanunk a legfontosabb segédváltozókat. Ezt csak egyszer szükséges elvégezni egy adott 3D modellhez.


void init_3D(void)
{
rot_degree_x = 0 * Math_PI / 180; rot_degree_x2 = 0;
rot_degree_y = 0 * Math_PI / 180; rot_degree_y2 = 0;
rot_degree_z = 0 * Math_PI / 180; rot_degree_z2 = 0;
raw_vertices_length = 0;
}


2.3 Forgatás és perspektivikus projekció

A legfontosabb művelet 3D esetében a forgatás, mivel ennek a segítségével szemléltethető legjobban a térbeliség.
Kezdjük a koordináták elforgatásának algoritmusaival!
Persze ehhez a térbeli adatszerkezeteinket ezt megelőzően a megfelelő módon fel kell tölteni adatokkal: háromszögek csúcspontkoordinátáit kell tartalmazniuk szekvenciálisan.

A kiindulási pont koordinátái: X1, Y1, Z1
Az elforgatás utáni koordináták: X2, Y2, Z2

Az X tengely körüli forgatás algoritmusa általánosan, a 'szog' szöggel elforgatva egy modell csúcspontjait:


X2 = X1
Y2 = Y1 * COS (szog) - Z1 * SIN (szog)
Z2 = - Y1 * SIN (szog) + Z1 * COS (szog)


Az Y tengely körüli forgatás algoritmusa:


X2 = X1 * COS (szog)+ Z1 * SIN (szog)
Y2 = Y1
Z2 = X1 * SIN (szog) - Z1 * COS(szog)


Az Z tengely körüli forgatás:


X2 = X1 * COS (szog) - Y1 * SIN (szog)
Y2 = X1 * SIN (szog) + Y1 * COS (szog)
Z2 = Z1


Csupán egyetlen tengely körüli forgatáshoz a fenti képletek külön-külön is elegendőek. Több tengely körüli elforgatás esetén azonban a forgatásokat egymás után kell végrehajtani. Ilyenkor viszont csak az első forgatás során kell az X1, Y1, Z1 koordinátákat figyelembe venni, utána elegendő a már kiszámolt X2, Y2, Z2 koordinátákat tovább forgatni.
Az alábbi, rotation függvényben közölt algoritmus a forgatást és az azt követő projekciót (a model koordinátáinak képernyőkoordinátákká történő átkonvertálását) végzi el. Az egész műveletsort kissé optimalizáltuk, ezáltal jó néhány feleslegesen ismétlődő  matematikai műveletet megspórolva.
A függvény bemenetként a nyers és az elforgatott csúcspontokat tartalmazó tömböket, a csúcspontok számát, valamint az elforgatás szögeit várja bemenetként.


void rotation(int maxitemcount,
float* rawarrayX, float* rawarrayY, float* rawarrayZ,
float* rotarrayX, float* rotarrayY, float* rotarrayZ,
float degree_cosx, float degree_sinx,
float degree_cosy, float degree_siny,
float degree_cosz, float degree_sinz)


Néhány segédváltozó deklarálása után az összes csúcspontra elvégeztetjük a forgatást.


{
int i;
float t0;

//forgatas
for (i = 0; i < maxitemcount; ++i)
{
 rotarrayY[i] = (rawarrayY[i] * degree_cosx) - (rawarrayZ[i] * degree_sinx);
 rotarrayZ[i] = rawarrayY[i] * degree_sinx + rawarrayZ[i] * degree_cosx;

 rotarrayX[i] = rawarrayX[i] * degree_cosy + rotarrayZ[i] * degree_siny;
 rotarrayZ[i] = -rawarrayX[i] * degree_siny + rotarrayZ[i] * degree_cosy;// +

 t0 = rotarrayX[i];
 //manuális finomhangolás : "+ (SCREEN_WIDTH / 4)" and "+ (SCREEN_HEIGHT / 4)"
 rotarrayX[i] = t0 * degree_cosz - rotarrayY[i] * degree_sinz + (SCREEN_WIDTH / 4);
 rotarrayY[i] = t0 * degree_sinz + rotarrayY[i] * degree_cosz + (SCREEN_HEIGHT / 4);
}


A projekció végrehajtása előtt néhány segédváltozóban eltárolunk bizonyos értékeket, hogy megspóroljuk ezek redundáns végrehajtását.
A projekció ellenőrzi, hogy az aktuális háromszög bármely pontja kilóg-e a kétdimenziós képernyő keretein kívülre, ill. a Z tengely mentén nincsen-e túl közel. Ezeket a háromszögeket nem veszi figyelembe a későbbiekben.


//perspektivikus projekcio
int s1;
float sx = SCREEN_WIDTH / 2;
float sultra = SCREEN_HEIGHT / 2, sultra2 = SCREEN_HEIGHT / 3;
int x_minusz_edge = 0, y_minusz_edge = 0, x_max_edge = SCREEN_WIDTH - 1, y_max_edge = SCREEN_HEIGHT - 1;
float distance;

zorder_length = 0;

for (i = 0; i < maxitemcount; i+=3)
{
 distance = 999999;

for (s1 = i; s1 < i + 3; ++s1)
 {
  if (rotarrayZ[s1] < distance) distance = rotarrayZ[s1];
  if (distance < viewpoint) { rotarrayZ[s1] = -9999999; continue; }
  sultra = viewpoint / (viewpoint - rotarrayZ[s1]);
  rotarrayX[s1] = rotarrayX[s1] * sultra + 400;
  rotarrayY[s1] = (rotarrayY[s1] * sultra) + sultra2;
  if (rotarrayX[s1] < x_minusz_edge || rotarrayX[s1] > x_max_edge)
  { rotarrayZ[s1] = -9999999; continue; }
  if (rotarrayY[s1] < y_minusz_edge || rotarrayY[s1] > y_max_edge)
  { rotarrayZ[s1] = -9999999; continue; }
 }


Végezetül előkészítjük a mélységi(Z) koordinátákat tartalmazó tömböt, mely a végső kirajzoláshoz kell majd. Továbbá ezt a tömböt lehet használni a mélységi rendezéshez is. Minden Z-kooridnátához eltároljuk az adott háromszög indexét is.


 zorder_index[zorder_length][0] = i;
 zorder_distance[zorder_length++] = distance;
}
}



2.4 Mélységi rendezés

A 3D programozás egyik sarkalatos pontja az alakozatok takarási viszonyainak meghatározása. Erre azért van szükség, mert bármilyen objektum esetén az egyes poligonok (legtöbbször háromszögek) „ömlesztve” vannak eltárolva. Amennyiben egyszerűen szekvenciálisan rajzolnánk ki őket, szinte minden esetben pontatlan eredmény születne.
A számítógépes grafikában a két alapvető megoldási módszer a raszterizáció z-puffer megoldása és a sugárkövetés módszerei.
Ezen a helyen viszont megelégszünk egy jóval egyszerűbb megoldással is: az alakzatokat Z-koordinátáik alapján virtuálisan sorrendbe rakjuk.
Ez egy közepesen lassú megoldás, kisebb modellek esetében mégis kielégítő sebességet nyújt (pár százezer csúcspont mennyiségig).
A rendezés algoritmusa:


void shell_sorting(void)
{
int i,j,k;
int swap0;
float swap2;

for (k = zorder_length / 2; k > 0; k = k / 2)
 for (i = k; i < zorder_length; ++i)
  for (j = i - k; (j >= 0) && (zorder_distance[j] > zorder_distance[j + k]); j = j - k)
  {
   swap0 = zorder_index[j];
   swap2 = zorder_distance[j];
   zorder_index[j] = zorder_index[j + k];
   zorder_distance[j] = zorder_distance[j + k];
   zorder_index[j + k] = swap0;
   zorder_distance[j + k] = swap2;
  }
}


2.5 Zoomolás

A 3D-s modellek megtekintésekor alapvető szükség lehet a jelenet nagyítására és kicsinyítésére. A következő függvények előkészítik a csúcspontokat ehhez.


void zoom_in(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ)
{
int i;
for (i = 0; i < maxitemcount; ++i)
{
 rawarrayX[i] *= 1.2;
 rawarrayY[i] *= 1.2;
 rawarrayZ[i] *= 1.2;
}
}

void zoom_out(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ)
{
int i;
for (i = 0; i < maxitemcount; ++i)
{
 rawarrayX[i] /= 1.2;
 rawarrayY[i] /= 1.2;
 rawarrayZ[i] /= 1.2;
}
}


2.6 Kirajzolás a képernyőre

Az alábbi függvény maga kezeli a renderelés előkészítését és végrehajtását. A render_objects függvény tartalmazza az összes Direct2D rajzmetódust.
Minden mást a render_scene függvény végez el.
A konkrét rajzmetódusokat a következő sorrendben hívjuk meg:
• rotation
• rendezes_shell
• render_objects.

A D2D_drawing függvényen belül, Direct2D-ben BeginDraw() és EndDraw() metódushívások közé helyezzük el a rajzmetódusokat. A közölt példakódban kitöltött alakzatokat és vonalas, ún. “drótvázmodelles” megjelenítéshez szükséges kódokat is elhelyeztünk, utóbbiakat kikommenteztük.
Direct2D esetén az alábbi rajzmetódusokat alkalmazzuk:
• AddTriangles: háromszögek közvetlen megadása.
• CreateMesh, FillMesh: a háromszögek kirajzolása ún. Mesh objektumokon keresztül történik.
• DrawLine: vonalrajzolás metódusa.

Mindent egyetlen CPU szálon hajtunk végre.


void render_scene(void)
{
rot_degree_x = rot_degree_x2 * Math_PI / 180;
rot_degree_y = rot_degree_y2 * Math_PI / 180;
rot_degree_z = rot_degree_z2 * Math_PI / 180;
float degree_sinx = sin(rot_degree_x);
float degree_cosx = cos(rot_degree_x);
float degree_siny = sin(rot_degree_y);
float degree_cosy = cos(rot_degree_y);
float degree_sinz = sin(rot_degree_z);
float degree_cosz = cos(rot_degree_z);

rotation(raw_vertices_length, raw_verticesX, raw_verticesY, raw_verticesZ, rotated_verticesX, rotated_verticesY, rotated_verticesZ, degree_cosx, degree_sinx, degree_cosy, degree_siny, degree_cosz, degree_sinz);

rendezes_shell();

render_objects(raw_vertices_length, rotated_verticesX, rotated_verticesY, rotated_verticesZ);
}

void D2D_drawing(int maxitemcount, float* rotarrayX, float* rotarrayY, float* rotarrayZ)
{
int i, px, py, drawcolor;
VEKTOR Vector1, Vector2, vNormal, vNormalized;//for visibility check
float Light_intensity, Vector_length;

pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
pRT->BeginDraw();
pRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
for (i = zorder_length; i >= 0 ; --i)
{
 if ((rotarrayZ[zorder_index[i]] < -9000000) || (rotarrayZ[zorder_index[i] + 1] < -9000000) || (rotarrayZ[zorder_index[i] + 2] < -9000000)) continue;

 // lathatosagi vizsgalathoz
 Vector1.x = rotarrayX[zorder_index[i] + 1] - rotarrayX[zorder_index[i]];
 Vector1.y = rotarrayY[zorder_index[i] + 1] - rotarrayY[zorder_index[i]];
 Vector1.z = rotarrayZ[zorder_index[i] + 1] - rotarrayZ[zorder_index[i]];
 Vector2.x = rotarrayX[zorder_index[i] + 2] - rotarrayX[zorder_index[i]];
 Vector2.y = rotarrayY[zorder_index[i] + 2] - rotarrayY[zorder_index[i]];
 Vector2.z = rotarrayZ[zorder_index[i] + 2] - rotarrayZ[zorder_index[i]];

 vNormal.x = ((Vector1.y * Vector2.z) - (Vector1.z * Vector2.y));
 vNormal.y = ((Vector1.z * Vector2.x) - (Vector1.x * Vector2.z));
 vNormal.z = ((Vector1.x * Vector2.y) - (Vector1.y * Vector2.x));
 if (vNormal.z > 0) continue;
 //*/

 Vector_length = sqrtf((vNormal.x * vNormal.x) + (vNormal.y * vNormal.y) + (vNormal.z * vNormal.z));
 vNormalized.x = vNormal.x / Vector_length;
 vNormalized.y = vNormal.y / Vector_length;
 vNormalized.z = vNormal.z / Vector_length;
 Light_intensity = ((vNormalized.x * vLight.x) + (vNormalized.y * vLight.y) + (vNormalized.z * vLight.z));
 if (Light_intensity > 1) Light_intensity = 1;
 else if (Light_intensity < 0) Light_intensity = 0;

 //drawcolor = RGB(180 * ((float)i / (float)maxitemcount * 100), 180 * ((float)i / (float)maxitemcount * 100), 180 * ((float)i / (float)maxitemcount * 100));
 drawcolor = RGB(255* Light_intensity,255* Light_intensity,255* Light_intensity);
 triangle.point1 = D2D1::Point2F(rotarrayX[zorder_index[i]], rotarrayY[zorder_index[i]]);
 triangle.point2 = D2D1::Point2F(rotarrayX[zorder_index[i] +1], rotarrayY[zorder_index[i] +1]);
 triangle.point3 = D2D1::Point2F(rotarrayX[zorder_index[i] +2], rotarrayY[zorder_index[i] +2]);
 ID2D1SolidColorBrush* ecset;
 pRT->CreateSolidColorBrush(D2D1::ColorF(drawcolor, 1.0f),&ecset);
   
 //kitoltott haromszogek
 ID2D1Mesh* pMesh = NULL;
 ID2D1TessellationSink* tessSink = NULL;
 pRT->CreateMesh(&pMesh);
 pMesh->Open(&tessSink);
 tessSink->AddTriangles(&triangle, 1);
 tessSink->Close();
 pRT->FillMesh(pMesh, ecset);
 tessSink->Release();
 pMesh->Release();//*/

 //csak drotvazmodell
 /*pRT->DrawLine(
  D2D1::Point2F(rotarrayX[zorder_index[i]], rotarrayY[zorder_index[i]]),
  D2D1::Point2F(rotarrayX[zorder_index[i] + 1], rotarrayY[zorder_index[i] + 1]),
  ecset,
  1.0f);
 pRT->DrawLine(
  D2D1::Point2F(rotarrayX[zorder_index[i]+2], rotarrayY[zorder_index[i]+2]),
  D2D1::Point2F(rotarrayX[zorder_index[i] + 1], rotarrayY[zorder_index[i] + 1]),
  ecset,
  1.0f);
 pRT->DrawLine(
  D2D1::Point2F(rotarrayX[zorder_index[i]], rotarrayY[zorder_index[i]]),
  D2D1::Point2F(rotarrayX[zorder_index[i] + 2], rotarrayY[zorder_index[i] + 2]),
  ecset,
  1.0f);//*/
}
pRT->EndDraw();
}



3 3D modellek megjelenítése

Ezen leírás letölthető, teljes példakódja WavefrontOBJ formátumú 3D modellek betöltését és megjelenítését végzi.
Ehhez kell egy egyszerű OBJ formátumú betöltőt is, ennek forráskódja:


float tomb_vertices[MAX_OBJ_NUM][3];
int tomb_faces[MAX_OBJ_NUM][5];
int tomb_vertices_length = 0, tomb_faces_length = 0;

void obj_loader(void)
{
FILE* objfile;
int i, j;
float data1, data2, data3;
unsigned char row1[1024], row2[1024];
int data_count, max_row_length = 250;
char tempstr[200];

objfile = fopen("MODELL.OBJ", "rt");
if (objfile == NULL) return;

tomb_vertices_length = tomb_vertices_length = 0;

while (!feof(objfile))
{
 fgets((char*)row1, max_row_length, objfile);

 if (row1[0] == 118 && row1[1] == 32) //*** 'v '
 {
  getelement(row1, 1, row2); data1 = atof((const char*)row2);
  getelement(row1, 2, row2); data2 = atof((const char*)row2);
  getelement(row1, 3, row2); data3 = atof((const char*)row2);
  tomb_vertices[tomb_vertices_length][0] = data1 * 4;
  tomb_vertices[tomb_vertices_length][1] = data2 * 4;
  tomb_vertices[tomb_vertices_length++][2] = data3 * 4;
 }
 else if (row1[0] == 102 && row1[1] == 32) //*** 'f '
 {
  data_count = getelementcount(row1);

  tomb_faces[tomb_faces_length][0] = data_count;
  for (i = 1; i < data_count + 1; ++i)
  {
   getelement(row1, i, row2);
   data1 = atof((const char*)row2);
   tomb_faces[tomb_faces_length][i] = data1 - 1;
  }
  ++tomb_faces_length;
 }
}
fclose(objfile);
int  base_index;
for (i = 0; i < tomb_faces_length; ++i)
{
 base_index = tomb_faces[i][1];
 if (tomb_faces[i][0] == 3)
 {
  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][1]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][2]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][3]][2];
 }
 else if (tomb_faces[i][0] == 4)
 {
  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][1]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][2]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][3]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][1]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][3]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][4]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][4]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][4]][2];
 }
}
}


Nézzünk meg néhány példát egy 400000-nél több csúcspontot tartalmazó modellre alapozva!
Az első példában a vonalakat alakzatok rajzolására használjuk. Ez hasznos komplex objektumok megjelenítéséhez.



A következő példa bemutatja kitöltött háromszögek használatát. A színezés kvázi véletlenszerű színekkel történt.



Számos weboldal ingyenesen használható modelleket kínál OBJ formátumban, szóval könnyedén beszerezhetünk ilyen modelleket.
Érdemes azonban az OBJ fájl tartalmát felhasználás előtt „megtisztítani”, hogy csak a számunkra fontos adatokat tartalmazzák, ami sok esetben megkönnyíti azok sikeres beolvasását és feldolgozását is.
A Blender (https://www.blender.org/) szerkesztőként ajánlott ehhez a művelethez, teljesen kikapcsolt exportálási lehetőségekkel (semmi nincs bejelölve az exportálás dialógusablakában).

3.1 Láthatósági tesztek

A rajzolandó háromszögek láthatóságának megvizsgálásával további, kb. kétszeres sebességnövekedés érhető el, mivel ilyenkor átlagban a háromszögek felét nem kell kirajzolni.
Ehhez a művelethez vektorokra van szükségünk.


struct VEKTOR {
float x;
float y;
float z;
};
VEKTOR Vector1, Vector2, vNormal;


A láthatóság úgy határozható meg, hogy kiszámoljuk az adott háromszög normálvektorát.


Vector1.x = rotarrayX[zorder_index[i][0] + 1] - rotarrayX[zorder_index[i][0]];
Vector1.y = rotarrayY[zorder_index[i][0] + 1] - rotarrayY[zorder_index[i][0]];
Vector1.z = rotarrayZ[zorder_index[i][0] + 1] - rotarrayZ[zorder_index[i][0]];
Vector2.x = rotarrayX[zorder_index[i][0] + 2] - rotarrayX[zorder_index[i][0]];
Vector2.y = rotarrayY[zorder_index[i][0] + 2] - rotarrayY[zorder_index[i][0]];
Vector2.z = rotarrayZ[zorder_index[i][0] + 2] - rotarrayZ[zorder_index[i][0]];

vNormal.x = ((Vector1.y * Vector2.z) - (Vector1.z * Vector2.y));
vNormal.y = ((Vector1.z * Vector2.x) - (Vector1.x * Vector2.z));
vNormal.z = ((Vector1.x * Vector2.y) - (Vector1.y * Vector2.x));
if (vNormal.z > 0) continue;


Szintén bevett szokás a normálvektor normalizálása, de ha valóban csak a láthatóság ellenőrzésére van szükségünk, akkor elegendő a vektorok keresztszorzatának előjelét ellenőrizni, mint a fenti mintakódban.
A gyakorlatban érdemes egy ilyen tesztet elhelyezni a renderelési függvényeinkben, mielőtt egy háromszöget kirajzolunk. A kódrészlet a letölthető példaprogram kódjában is megtalálható. Ezt a tesztet általában hátsólap eldobásnak (angolul: backface culling) nevezik. Bizonyos esetekben nem kívánatos a háromszögek szűrése, ezért érdemes megfontolni ennek a módszernek a használatát, de a legtöbb esetben érdemes legalább kipróbálni.

3.2 Textúrák használata

Az itt bemutatott rajzoló algoritmusok vektoros megjelenítést használnak. Az egyszerű vektorgrafika több célra is alkalmas lehet, de felmerül a kérdés, hogy lehetséges-e ezt a grafikus megjelenítő motort egy kicsit kibővíteni.
A texturák használata lényegében a színértékek pontokhoz vagy alakzatokhoz történő rendelése. Ehhez fel kell töltenünk egy textúra térképet, amelyet kvázi logikus referencia-színtérképként lehet használni.
Az algoritmusok egyedi színértékeinek meghatározása nagyon elegánsan elvégezhető, mivel pixelszinten is dolgozunk.
A csúcspontok színének megállapítását a következő példakód illusztrálja, melyben int típusú raw_vertices_colors és texture  tömbökre hivatkozunk.

raw_vertices_colors[raw_vertices_color_length++] = texture[texture_index];
Rajtunk múlik, hogyan állítjuk be a textúrák tömbjének indexét kódjainkba, azaz milyen logika szerint határozzuk meg az egyes pixelek/háromszögek színeit. Talán a legegyszerűbb a négyzet alakú területek használata.
Itt mutatunk be egy rendkívül minimalista 24 bites BMP képbetöltő függvényt is, amely 400x400 pixel textúra-tömböt tölt be és a Direct2D RGB makrójával a konkrét színértékeket tárolja el egy szín-referenciatömbben.


void load_bmptexture(unsigned int *texture)
{
FILE *bmp_file;
int i, j,s=0;
unsigned char R, G, B;
bmp_file = fopen("texture.bmp","rb");
if (bmp_file == NULL) return;
fseek(bmp_file,54, SEEK_SET);
for(i=0;i<400;++i)
 for (j = 0; j < 400; ++j)
 {
  fread(&B,1,1,bmp_file);
  fread(&G,1,1, bmp_file);
  fread(&R,1,1, bmp_file);
  texture[s++] = RGB(B,G,R);
 }
fclose(bmp_file);
}


Íme egy példa a textúrák (műholdképek) 3D-s alkalmazására egy valódi programban:



3.3 Fények alkalmazása

Néhány további kódsorral megvilágítást is szimulálhatunk. A fény megadásához használhatunk egy globális vektort:


VEKTOR vLight;


Ezt egyszer kell inicializálni, melynek során a fény irányát adjuk meg:


vLight.x = -0.5; vLight.y = -0.5; vLight.z = -0.9;


A D2D_drawing függvényben ki kell számolnunk a normálvektort minden háromszögre, és a vektort szintén normalizálni kell:


float Light_intensity,Vector_length;
VEKTOR Vector1, Vector2, vNormal,vNormalized;//for visibility check

Vector_length = sqrtf( (vNormal.x*vNormal.x) + (vNormal.y*vNormal.y) + (vNormal.z*vNormal.z) );
vNormalized.x = vNormal.x / Vector_length;
vNormalized.y = vNormal.y / Vector_length;
vNormalized.z = vNormal.z / Vector_length;


A nyers számított fényerősség felhasználható színek interpolálására, így lehet szimulálni 3D-s modellek megvilágítását:


Light_intensity = ((vNormalized.x * vLight.x) + (vNormalized.y * vLight.y) + (vNormalized.z * vLight.z));
if (Light_intensity > 1) Light_intensity = 1;
else if (Light_intensity < 0) Light_intensity = 0;


Példa szín beállítására a fény függvényében:


drawcolor = RGB(250* Light_intensity, 250* Light_intensity,
250* Light_intensity);  




3.4 Teljesítménymérés, statisztikák

Nagyon hasznos tudni, hogy számítógépünk milyen sebességgel képes megjeleníteni objektumokat. Általában 10-15 képkocka/másodperc képváltási sebesség már a folyamatosság érzetét képes kelteni. Manapság a 60 fps érték az etalon.
Hogy pontosan lássunk, megmérhetjük a rendereléshez szükséges időt. Ehhez először lekérdezzük a renderelés megkezdése előtt a program elindítása óta eltelt időt, majd ezt az értéket kivonjuk a renderelés végén lekérdezett időből. A közismert fps értéket úgy kaphatjuk meg, ha 1000-ből kivonjuk a két idő különbségét.
Ehhez az alábbi változókra lesz szükségünk:


float fps_stat;
ULONGLONG starttime, endtime;
A megfelelő értékek kiszámítsa így történhet:

starttime = GetTickCount64();

endtime = GetTickCount64();
if ((endtime - starttime) == 0) ++endtime;
fps_stat = 1000 / (endtime - starttime);


Érdekes lehet látni, hogy hány csúcspontból, ill. háromszögből áll a megjelenített modell. Ezt a legegyszerűbben a vertex_counter s a poly_counter változók kiírásával tudjuk megtenni.
Helytakarékos megjelenítéshez jutunk, ha a mért értékeket az ablak címsorában jelenítjük meg:


char tempstr[255], tempstr2[255];

strcpy(tempstr2, "Vertices: ");
_itoa(vertex_counter, tempstr, 10);
strcat(tempstr2, tempstr);

strcat(tempstr2, " Triangles: ");
_itoa(poly_counter, tempstr, 10);
strcat(tempstr2, tempstr);

strcat(tempstr2, " Z ordered: ");


strcat(tempstr2, " FPS: ");
_itoa(fps_stat, tempstr, 10); strcat(tempstr2, tempstr);

strcat(tempstr2, ", X: ");
_itoa(rot_degree_x2, tempstr, 10); strcat(tempstr2, tempstr);

strcat(tempstr2, ", Y: ");
_itoa(rot_degree_y2, tempstr, 10); strcat(tempstr2, tempstr);

strcat(tempstr2, ", Z: ");
_itoa(rot_degree_z2, tempstr, 10); strcat(tempstr2, tempstr);

SetWindowTextA(Form1, tempstr2);


A fenti kódokat a render_scene függvényben kell elhelyeznünk.
További értékes információhoz juthatunk azáltal, ha megszámoljuk és kiírjuk a ténylegesen, aktuálisan kirajzolt háromszögek számát. Ehhez a háromszögek megszámlálását a D2D_drawing függvényben kell elvégeznünk.

3.5 Záró gondolatok

A 3D programozást bemutató leírás végéhez értünk. Remélem, hogy a kedves olvasó a leírás elolvasása után még motiváltabban fog hozzá 3D grafikát alkalmazó programok fejlesztéséhez.
4 Függelék – teljes forráskód

Alább a leírás tárgyát képező technikák bemutatását egy teljes programkód formájában is leközöljük. Ez ugyanaz a github-ról letölthető progamkód, amire az Előszóban történt hivatkozás.
A programmal megjeleníthetünk Wavefront OBJ modelleket, és elforgathatjuk azokat egy gamepad játékvezérlővel.




#include <math.h>
#include <stdio.h>
#include <windows.h>
#include <d2d1.h>
#include <d2d1helper.h>
#pragma comment(lib, "d2d1")
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

#define SCREEN_WIDTH 1920
#define SCREEN_HEIGHT 1000

struct VEKTOR {
float x;
float y;
float z;
};
VEKTOR vLight;
D2D1_TRIANGLE triangle;

#define MAX_OBJ_NUM 20000000
int viewpoint = -1100;
float rot_degree_x;
float rot_degree_y;
float rot_degree_z;
float rot_degree_x2 = 0;
float rot_degree_y2 = 90.0f;
float rot_degree_z2 = 0;
float Math_PI = 3.14159265358979323846;
float raw_verticesX[MAX_OBJ_NUM], raw_verticesY[MAX_OBJ_NUM], raw_verticesZ[MAX_OBJ_NUM];
float rotated_verticesX[MAX_OBJ_NUM], rotated_verticesY[MAX_OBJ_NUM], rotated_verticesZ[MAX_OBJ_NUM];
int raw_vertices_length;

int zorder_length;
int zorder_index[MAX_OBJ_NUM];
float zorder_distance[MAX_OBJ_NUM];

void shell_sorting(void);
void init_3D(void);
void render_scene(void);
void rotation(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ, float* rotarrayX, float* rotarrayY, float* rotarrayZ, float degree_cosx, float degree_sinx, float degree_cosy, float degree_siny, float degree_cosz, float degree_sinz);
void D2D_drawing(int maxitemcount, float* rotarrayX, float* rotarrayY, float* rotarrayZ);
void zoom_in(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ);
void zoom_out(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ);
//************************************

//***********STANDARD WIN32API************
ID2D1Factory* pD2DFactory = NULL;
ID2D1HwndRenderTarget* pRT = NULL;
#define HIBA_00 TEXT("Error:Program initialisation process.")
HINSTANCE hInstGlob;
int SajatiCmdShow;
HWND Form1; //Ablak kezeloje
LRESULT CALLBACK WndProc0(HWND, UINT, WPARAM, LPARAM);
//******************************************************

//********************************
//OBJ formatum kezelesehez
//********************************
float tomb_vertices[MAX_OBJ_NUM][3];
int tomb_faces[MAX_OBJ_NUM][5];
int tomb_vertices_length = 0, tomb_faces_length = 0;
int getelementcount(unsigned char csv_content[]);
void getelement(unsigned char csv_content[], unsigned int data_index, unsigned char csv_content2[]);
void obj_loader(void);

//*********************************
//Foprogram belepesi pont
//*********************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("StdWinClassName");
HWND hwnd;
MSG msg;
WNDCLASS wndclass0;
SajatiCmdShow = iCmdShow;
hInstGlob = hInstance;

//*********************************
//Ablak elokeszitese
//*********************************
wndclass0.style = CS_HREDRAW | CS_VREDRAW;
wndclass0.lpfnWndProc = WndProc0;
wndclass0.cbClsExtra = 0;
wndclass0.cbWndExtra = 0;
wndclass0.hInstance = hInstance;
wndclass0.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass0.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass0.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
wndclass0.lpszMenuName = NULL;
wndclass0.lpszClassName = TEXT("WIN0");

//*********************************
//Ablak regisztrralasa
//*********************************
if (!RegisterClass(&wndclass0))
{
 MessageBox(NULL, HIBA_00, TEXT("Program Start"), MB_ICONERROR);
 return 0;
}

//*********************************
//Ablak letrehozasaCreating the window
//*********************************
Form1 = CreateWindow(TEXT("WIN0"),
 TEXT("CUDA - DIRECT2D"),
 (WS_OVERLAPPED | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX),
 0,
 0,
 SCREEN_WIDTH,
 SCREEN_HEIGHT,
 NULL,
 NULL,
 hInstance,
 NULL);

//*********************************
//Ablak megjelenitese
//*********************************
ShowWindow(Form1, SajatiCmdShow);
UpdateWindow(Form1);

//*********************************
//Ablak mukodtetese
//*********************************
while (GetMessage(&msg, NULL, 0, 0))
{
 TranslateMessage(&msg);
 DispatchMessage(&msg);
}
return msg.wParam;
}

//*********************************
//Uzenetkezeles
//*********************************
LRESULT CALLBACK WndProc0(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
unsigned int xPos, yPos, fwButtons;

switch (message)
{
 //*********************************
 //Ablak letrehozasa
 //*********************************
case WM_CREATE:
 D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
 pD2DFactory->CreateHwndRenderTarget(
  D2D1::RenderTargetProperties(),
  D2D1::HwndRenderTargetProperties(
   hwnd, D2D1::SizeU(SCREEN_WIDTH, SCREEN_HEIGHT)),
  &pRT);
 init_3D();
 obj_loader();
 if ((joyGetNumDevs()) > 0) joySetCapture(hwnd, JOYSTICKID1, NULL, FALSE);
 return 0;
 //*********************************
 //Villodzas ellen
 //*********************************
case WM_ERASEBKGND:
 return (LRESULT)1;
case MM_JOY1MOVE:
 fwButtons = wParam;
 xPos = LOWORD(lParam);
 yPos = HIWORD(lParam);
 if (xPos == 65535) {
  rot_degree_y2 += 5.0; render_scene();
 }
 else if (xPos == 0) {
  rot_degree_y2 -= 5.0; render_scene();
 }
 if (yPos == 65535) {
  rot_degree_x2 += 5.0; render_scene();
 }
 else if (yPos == 0) {
  rot_degree_x2 -= 5.0; render_scene();
 }
 if (fwButtons == 128) {
  rot_degree_z2 += 5.0; render_scene();
 }
 else if (fwButtons == 64) {
  rot_degree_z2 -= 5.0; render_scene();
 }
 if (rot_degree_y2 > 360) {
  rot_degree_y2 = 0; render_scene();
 }
 else if (rot_degree_y2 < 0) {
  rot_degree_y2 = 358; render_scene();
 }
 if (rot_degree_x2 > 359) {
  rot_degree_x2 = 0; render_scene();
 }
 else if (rot_degree_x2 < 0) {
  rot_degree_x2 = 358; render_scene();
 }
 if (rot_degree_z2 > 359) {
  rot_degree_z2 = 0; render_scene();
 }
 else if (rot_degree_z2 < 0) {
  rot_degree_z2 = 358; render_scene();
 }

 if (fwButtons == 2)
 {
  zoom_in(raw_vertices_length, raw_verticesX, raw_verticesY, raw_verticesZ);
  render_scene();
 }
 else if (fwButtons == 4)
 {
  zoom_out(raw_vertices_length, raw_verticesX, raw_verticesY, raw_verticesZ);
  render_scene();
 }
 else if (fwButtons == 1)
 {
  viewpoint += 100;
  render_scene();
 }
 else if (fwButtons == 8)
 {
  viewpoint -= 100;
  render_scene();
 }
 break;
 //*********************************
 //Ablak ujrarajzolasa
 //*********************************
case WM_PAINT:
 hdc = BeginPaint(hwnd, &ps);
 EndPaint(hwnd, &ps);
 render_scene();
 return 0;
 //*********************************
 //Ablak bezarasa
 //*********************************
case WM_CLOSE:
 pRT->Release();
 pD2DFactory->Release();
 DestroyWindow(hwnd);
 return 0;
 //*********************************
 //Ablak megsemmisitese
 //*********************************
case WM_DESTROY:
 PostQuitMessage(0);
 return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

//********************************
//PEGAZUS 3D - INIT
//********************************
void init_3D(void)
{
rot_degree_x = 0 * Math_PI / 180; rot_degree_x2 = 0;
rot_degree_y = 0 * Math_PI / 180; rot_degree_y2 = 0;
rot_degree_z = 0 * Math_PI / 180; rot_degree_z2 = 0;
vLight.x = -0.5; vLight.y = -0.5; vLight.z = -0.9;
raw_vertices_length = 0;
}

//********************************
//OBJ formatum kezelese
//********************************
int getelementcount(unsigned char csv_content[])
{
int s1, s2;
for (s1 = s2 = 0; s1 < strlen((const char*)csv_content); ++s1)
{
 if (csv_content[s1] == 10) break;
 else if (csv_content[s1] == 32) ++s2;
}
return s2;
}

void getelement(unsigned char csv_content[], unsigned int data_index, unsigned char csv_content2[])
{
int s1, s2, s3, s4 = 0;
for (s1 = 0, s2 = 0; s1 < strlen((const char*)csv_content); ++s1)
{
 if (csv_content[s1] == 32)
 {
  ++s2;
  if (s2 == data_index)
  {
   for (s3 = s1 + 1; s3 < strlen((const char*)csv_content); ++s3)
   {
    if (csv_content[s3] == 32 || csv_content[s3] == 10)
    {
     csv_content2[s4] = 0;
     return;
    }
    else csv_content2[s4++] = csv_content[s3];
   }
  }
 }
}
}

void obj_loader(void)
{
FILE* objfile;
int i, j;
float data1, data2, data3;
unsigned char row1[1024], row2[1024];
int data_count, max_row_length = 250;
char tempstr[200];

objfile = fopen("MODELL.obj", "rt");
if (objfile == NULL) return;

tomb_vertices_length = tomb_vertices_length = 0;

while (!feof(objfile))
{
 fgets((char*)row1, max_row_length, objfile);

 if (row1[0] == 118 && row1[1] == 32) //*** 'v '
 {
  getelement(row1, 1, row2); data1 = atof((const char*)row2);
  getelement(row1, 2, row2); data2 = atof((const char*)row2);
  getelement(row1, 3, row2); data3 = atof((const char*)row2);
  tomb_vertices[tomb_vertices_length][0] = data1 * 4;
  tomb_vertices[tomb_vertices_length][1] = data2 * 4;
  tomb_vertices[tomb_vertices_length++][2] = data3 * 4;
 }
 else if (row1[0] == 102 && row1[1] == 32) //*** 'f '
 {
  data_count = getelementcount(row1);

  tomb_faces[tomb_faces_length][0] = data_count;
  for (i = 1; i < data_count + 1; ++i)
  {
   getelement(row1, i, row2);
   data1 = atof((const char*)row2);
   tomb_faces[tomb_faces_length][i] = data1 - 1;
  }
  ++tomb_faces_length;
 }
}
fclose(objfile);
int  base_index;
for (i = 0; i < tomb_faces_length; ++i)
{
 base_index = tomb_faces[i][1];
 if (tomb_faces[i][0] == 3)
 {
  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][1]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][2]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][3]][2];
 }
 else if (tomb_faces[i][0] == 4)
 {
  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][1]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][2]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][2]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][3]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][1]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][1]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][3]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][3]][2];

  raw_verticesX[raw_vertices_length] = tomb_vertices[tomb_faces[i][4]][0];
  raw_verticesY[raw_vertices_length] = tomb_vertices[tomb_faces[i][4]][1];
  raw_verticesZ[raw_vertices_length++] = tomb_vertices[tomb_faces[i][4]][2];
 }
}
}

void render_scene(void)
{
char tempstr[255], tempstr2[255];
float fps_stat;
ULONGLONG starttime, endtime;

strcpy(tempstr2, "Vertices: ");
_itoa(raw_vertices_length, tempstr, 10);
strcat(tempstr2, tempstr);
strcat(tempstr2, " Triangles: ");
_itoa(raw_vertices_length/3, tempstr, 10);
strcat(tempstr2, tempstr);
strcat(tempstr2, " Z ordered: ");
starttime = GetTickCount64();

rot_degree_x = rot_degree_x2 * Math_PI / 180;
rot_degree_y = rot_degree_y2 * Math_PI / 180;
rot_degree_z = rot_degree_z2 * Math_PI / 180;
float degree_sinx = sin(rot_degree_x);
float degree_cosx = cos(rot_degree_x);
float degree_siny = sin(rot_degree_y);
float degree_cosy = cos(rot_degree_y);
float degree_sinz = sin(rot_degree_z);
float degree_cosz = cos(rot_degree_z);

rotation(raw_vertices_length, raw_verticesX, raw_verticesY, raw_verticesZ, rotated_verticesX, rotated_verticesY, rotated_verticesZ, degree_cosx, degree_sinx, degree_cosy, degree_siny, degree_cosz, degree_sinz);
shell_sorting();
D2D_drawing(raw_vertices_length, rotated_verticesX, rotated_verticesY, rotated_verticesZ);

endtime = GetTickCount64();
if ((endtime - starttime) == 0) ++endtime;
fps_stat = 1000 / (endtime - starttime); strcat(tempstr2, " FPS: "); _itoa(fps_stat, tempstr, 10); strcat(tempstr2, tempstr);
strcat(tempstr2, ", X: "); _itoa(rot_degree_x2, tempstr, 10); strcat(tempstr2, tempstr);
strcat(tempstr2, ", Y: "); _itoa(rot_degree_y2, tempstr, 10); strcat(tempstr2, tempstr);
strcat(tempstr2, ", Z: "); _itoa(rot_degree_z2, tempstr, 10); strcat(tempstr2, tempstr);
SetWindowTextA(Form1, tempstr2);
}

void rotation(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ, float* rotarrayX, float* rotarrayY, float* rotarrayZ, float degree_cosx, float degree_sinx, float degree_cosy, float degree_siny, float degree_cosz, float degree_sinz)
{
int i;
float t0;

//rotaion
for (i = 0; i < maxitemcount; ++i)
{
 rotarrayY[i] = (rawarrayY[i] * degree_cosx) - (rawarrayZ[i] * degree_sinx);
 rotarrayZ[i] = rawarrayY[i] * degree_sinx + rawarrayZ[i] * degree_cosx;

 rotarrayX[i] = rawarrayX[i] * degree_cosy + rotarrayZ[i] * degree_siny;
 rotarrayZ[i] = -rawarrayX[i] * degree_siny + rotarrayZ[i] * degree_cosy;// +

 t0 = rotarrayX[i];
 //Nemi finomhangolas OBJ modellek eseten: "+ (SCREEN_WIDTH / 4)" and "+ (SCREEN_HEIGHT / 4)"
 rotarrayX[i] = t0 * degree_cosz - rotarrayY[i] * degree_sinz + (SCREEN_WIDTH / 4);
 rotarrayY[i] = t0 * degree_sinz + rotarrayY[i] * degree_cosz + (SCREEN_HEIGHT / 4);
}

//persspektiva projekcio
int s1;
float sx = SCREEN_WIDTH / 2;
float sultra = SCREEN_HEIGHT / 2, sultra2 = SCREEN_HEIGHT / 3;
int x_minusz_edge = 0, y_minusz_edge = 0, x_max_edge = SCREEN_WIDTH - 1, y_max_edge = SCREEN_HEIGHT - 1;
float distance;

zorder_length = 0;

for (i = 0; i < maxitemcount; i+=3)
{
 distance = 999999;

 for (s1 = i; s1 < i + 3; ++s1)
 {
  if (rotarrayZ[s1] < distance) distance = rotarrayZ[s1];
  if (distance < viewpoint) { rotarrayZ[s1] = -9999999; continue; }
  sultra = viewpoint / (viewpoint - rotarrayZ[s1]);
  rotarrayX[s1] = rotarrayX[s1] * sultra + 400;
  rotarrayY[s1] = (rotarrayY[s1] * sultra) + sultra2;
  if (rotarrayX[s1] < x_minusz_edge || rotarrayX[s1] > x_max_edge) { rotarrayZ[s1] = -9999999; continue; }
  if (rotarrayY[s1] < y_minusz_edge || rotarrayY[s1] > y_max_edge) { rotarrayZ[s1] = -9999999; continue; }
 }

 zorder_index[zorder_length] = i;
 zorder_distance[zorder_length++] = distance;
}
}

void D2D_drawing(int maxitemcount, float* rotarrayX, float* rotarrayY, float* rotarrayZ)
{
int i, px, py, drawcolor;
VEKTOR Vector1, Vector2, vNormal, vNormalized;//for visibility check
float Light_intensity, Vector_length;

pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
pRT->BeginDraw();
pRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
for (i = zorder_length; i >= 0 ; --i)
{
 if ((rotarrayZ[zorder_index[i]] < -9000000) || (rotarrayZ[zorder_index[i] + 1] < -9000000) || (rotarrayZ[zorder_index[i] + 2] < -9000000)) continue;

 // lathatosagi vizsgalat
 Vector1.x = rotarrayX[zorder_index[i] + 1] - rotarrayX[zorder_index[i]];
 Vector1.y = rotarrayY[zorder_index[i] + 1] - rotarrayY[zorder_index[i]];
 Vector1.z = rotarrayZ[zorder_index[i] + 1] - rotarrayZ[zorder_index[i]];
 Vector2.x = rotarrayX[zorder_index[i] + 2] - rotarrayX[zorder_index[i]];
 Vector2.y = rotarrayY[zorder_index[i] + 2] - rotarrayY[zorder_index[i]];
 Vector2.z = rotarrayZ[zorder_index[i] + 2] - rotarrayZ[zorder_index[i]];

 vNormal.x = ((Vector1.y * Vector2.z) - (Vector1.z * Vector2.y));
 vNormal.y = ((Vector1.z * Vector2.x) - (Vector1.x * Vector2.z));
 vNormal.z = ((Vector1.x * Vector2.y) - (Vector1.y * Vector2.x));
 //if (vNormal.z > 0) continue;
 //*/

 Vector_length = sqrtf((vNormal.x * vNormal.x) + (vNormal.y * vNormal.y) + (vNormal.z * vNormal.z));
 vNormalized.x = vNormal.x / Vector_length;
 vNormalized.y = vNormal.y / Vector_length;
 vNormalized.z = vNormal.z / Vector_length;
 Light_intensity = ((vNormalized.x * vLight.x) + (vNormalized.y * vLight.y) + (vNormalized.z * vLight.z));
 if (Light_intensity > 1) Light_intensity = 1;
 else if (Light_intensity < 0) Light_intensity = 0;

 //drawcolor = RGB(180 * ((float)i / (float)maxitemcount * 100), 180 * ((float)i / (float)maxitemcount * 100), 180 * ((float)i / (float)maxitemcount * 100));
 drawcolor = RGB(255* Light_intensity,255* Light_intensity,255* Light_intensity);
 triangle.point1 = D2D1::Point2F(rotarrayX[zorder_index[i]], rotarrayY[zorder_index[i]]);
 triangle.point2 = D2D1::Point2F(rotarrayX[zorder_index[i] +1], rotarrayY[zorder_index[i] +1]);
 triangle.point3 = D2D1::Point2F(rotarrayX[zorder_index[i] +2], rotarrayY[zorder_index[i] +2]);
 ID2D1SolidColorBrush* ecset;
 pRT->CreateSolidColorBrush(D2D1::ColorF(drawcolor, 1.0f),&ecset);
   
 //kitoltott haromszogek
 ID2D1Mesh* pMesh = NULL;
 ID2D1TessellationSink* tessSink = NULL;
 pRT->CreateMesh(&pMesh);
 pMesh->Open(&tessSink);
 tessSink->AddTriangles(&triangle, 1);
 tessSink->Close();
 pRT->FillMesh(pMesh, ecset);
 tessSink->Release();
 pMesh->Release();//*/

 //csak drotvazmodell
 pRT->DrawLine(
  D2D1::Point2F(rotarrayX[zorder_index[i]], rotarrayY[zorder_index[i]]),
  D2D1::Point2F(rotarrayX[zorder_index[i] + 1], rotarrayY[zorder_index[i] + 1]),
  ecset,
  1.0f);
 pRT->DrawLine(
  D2D1::Point2F(rotarrayX[zorder_index[i]+2], rotarrayY[zorder_index[i]+2]),
  D2D1::Point2F(rotarrayX[zorder_index[i] + 1], rotarrayY[zorder_index[i] + 1]),
  ecset,
  1.0f);
 pRT->DrawLine(
  D2D1::Point2F(rotarrayX[zorder_index[i]], rotarrayY[zorder_index[i]]),
  D2D1::Point2F(rotarrayX[zorder_index[i] + 2], rotarrayY[zorder_index[i] + 2]),
  ecset,
  1.0f);//*/
}
pRT->EndDraw();
}

void zoom_in(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ)
{
int i;
for (i = 0; i < maxitemcount; ++i)
{
 rawarrayX[i] *= 1.2;
 rawarrayY[i] *= 1.2;
 rawarrayZ[i] *= 1.2;
}
}

void zoom_out(int maxitemcount, float* rawarrayX, float* rawarrayY, float* rawarrayZ)
{
int i;
for (i = 0; i < maxitemcount; ++i)
{
 rawarrayX[i] /= 1.2;
 rawarrayY[i] /= 1.2;
 rawarrayZ[i] /= 1.2;
}
}

void shell_sorting(void)
{
int i,j,k;
int swap0;
float swap2;

for (k = zorder_length / 2; k > 0; k = k / 2)
 for (i = k; i < zorder_length; ++i)
  for (j = i - k; (j >= 0) && (zorder_distance[j] > zorder_distance[j + k]); j = j - k)
  {
   swap0 = zorder_index[j];
   swap2 = zorder_distance[j];
   zorder_index[j] = zorder_index[j + k];
   zorder_distance[j] = zorder_distance[j + k];
   zorder_index[j + k] = swap0;
   zorder_distance[j + k] = swap2;
  }
}


KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
KAPCSOLAT

E-mail:
feher.konyvek@gmail.com
Vissza a tartalomhoz