Рендеринг тривимірної моделі
Недавно, я розказував про розшифрування формату 3ds. Це доволі поширений формат тривимірної графіки, підтримується більшістю редакторів, Blender в тому числі. І написав програмку, яка витягує з фалу 3ds дані, і записує їх в простіший текстовий формат. Але текст, це хоч і добре, але зображення завжди красивіше. Тому зараз спробуємо завантажити і намалювати тривимірну модель.
Клас, який буде відповідати за модель, назвемо TriMesh
(сітка трикутників), так як моделі в основному з трикутників і складаються.
Завантаження
Завантаження відбувається просто. Читаємо символ мітки блоку, і розмір блоку. Якщо мітка ‘N’, то далі читаємо рядок – ім’я мешу. Інакше, виділяємо достатню кількість пам’яті, і читаємо координати вершин, текстурні координати, чи номери вершин трикутників, залежно від мітки. Якщо зустріли якусь неправильну мітку – виводимо повідомлення про помилку.
Відтворення
Малюємо ще простіше. Вмикаємо режим виведення трикутників, цикл по всіх трикутниках, і в тілі виводимо всі три вершини. Але, тут починаються проблеми. Якби в мене була текстура, все би було не так і погано. Але, так як я її ще не намалював, і ще не завантажив, то виводиться тільки плоский силует моделі:
Обчислення
Правда частіше за все, ми взагалі нічого не побачимо, просто тому, що неправильно відредагували модель. Наприклад вона може не стояти в центрі системи координат, і виводитись за межами екрану. А ми будем морочити собі голову тим, чому вона не виводиться. В мене так і було. Але на щастя, я здогадався, і написав метод void computeboundaries()
, який обчислює центр обмежуючого паралелепіпеда для моделі, і переміщує модель так, щоб центр попав в точку (0,0,0). Також маштаб моделі може бути не таким як треба, але це я вже залишив на совість художника.
Ну, і виявляється в форматі 3ds переплутані осі Y і Z, тому я просто переставив ці дві змінні при завантаженні.
Щоб з’явилось світло, і наша модель виводилась об’ємною, потрібно обчислювати нормалі. Взагалі то нормалі обчислюються для кожної вершини, залежно від груп згладжування, але я вирішив поки що не морочити собі голову цими групами. В мене нормалі об’числюються для кожного трикутника.
Кожен трикутник лежить в площині яка задається двома векторами – якимись сторонами трикутника. Нормаль до площини – це нормалізований векторний добуток цих векторів. Оце й уся математика.
З світлом і нормалями наш корабель виглядатиме так:
Я правда змінив колір на сірий, бо чорний колір, робить з моделі абсолютно чорне тіло, а як відомо, абсолютно чорне тіло виглядає однаково, що при світлі, що в темряві. Ну, і насправді наша модель не така колюча, я просто намалював в центрі кожного трикутника нормаль, для наочності.
І ще одна думка з’явилась. Завантаження моделі я писав вчора. Сьогодні в обід встав, і виявив, що випадково, засинаючи, замінив код модуля з класом TriMesh нульовим проектом GLUT. І вбив працю всього дня. Тому от зараз написав його заново, і вирішив зберегти резезрвну копію онлайн. Бо це завантаження я пишу, вже далебі не перший раз. Треба збільшувати повторне використання коду. Як і обчислення векторного добутку.
Так отаке розчарування наштовхнуло мене на думку, що клас трикутної сітки треба спростити, викинувши зайві обчислення. Бо потім треба буде ще додавати текстури, і навіть можливо анімації. Тому в цей клас додам функцію збереження моделі, і просто зроблю програмку, яка зберігає результат переміщення в центр, і обчислення нормалей.
І варто зауважити, що код не зовсім безпечний. Якщо вам раптом повідомляють про Access Violation, чи про Segmentation fault (в *nіx системі), то перевірте, чи все завантажилось, і чи не забули ви обчислити нормалі.
Код
#ifndef MESH_H_INCLUDED #define MESH_H_INCLUDED #include <stdio.h> #include <math.h> typedef struct VertexS { float x,y,z; } Vertex; typedef struct TriangleS { int a,b,c; } Triangle; typedef struct MapCoordS { float u,v; } MapCoord; Vertex Normal(Vertex a, Vertex b, Vertex c) { Vertex u,v,res; u.x=b.x-a.x; u.y=b.y-a.y; u.z=b.z-a.z; v.x=c.x-a.x; v.y=c.y-a.y; v.z=c.z-a.z; res.x=-u.y*v.z+u.z*v.y; res.y=u.x*v.z-u.z*v.x; res.z=-u.x*v.y+u.y*v.x; return res; } Vertex Normalize(Vertex x) { float length=-sqrt(x.x*x.x+x.y*x.y+x.z*x.z); x.x/=length; x.y/=length; x.z/=length; return x; } class TriMesh { char name[30]; int vertcount,tricount; Vertex *vertexes; Triangle *triangles; Vertex* normals; MapCoord* mapping; public: TriMesh() { vertcount=0;tricount=0; vertexes=NULL;triangles=NULL; normals=NULL; } void clear() { if(vertexes) free(vertexes); if(triangles) free(triangles); if(normals) free(normals); vertcount=0; tricount=0; } void LoadFromFile(char *fn) { clear(); FILE *f; f=fopen(fn,"r"); if(!f) { printf("Error: can't open file \"%s\"\n",fn); return; } char label; int count; while(!feof(f)) { fscanf(f,"%c %i",&label,&count); switch(label) { case 'N': fscanf(f,"%s\n",name); break; case 'V': vertcount=count; vertexes=(Vertex *)malloc(sizeof(Vertex)*count); for(int i=0;i<count;i++) { fscanf(f,"%f %f %f\n",&vertexes[i].x,&vertexes[i].z,&vertexes[i].y); }; break; case 'P': tricount=count; triangles=(Triangle *)malloc(sizeof(Triangle)*count); for(int i=0;i<count;i++) { fscanf(f,"%i %i %i\n",&triangles[i].a,&triangles[i].b,&triangles[i].c); } break; case 'M': if(count!=vertcount) printf("Error: Count of mapping coords isn't equal to count of vertexes\n"); mapping=(MapCoord *)malloc(sizeof(MapCoord)*vertcount); for(int i=0;i<count;i++) { fscanf(f,"%f %f\n",&mapping[i].u,&mapping[i].v); } break; default: printf("Error: Unknown label: '%s'",label); break; } } fclose(f); } void computeboundaries() { if(!vertcount) return; Vertex min,max; min=max=vertexes[0]; for(int i=1;i<vertcount;i++) { if(min.x>vertexes[i].x) min.x=vertexes[i].x; if(min.y>vertexes[i].y) min.y=vertexes[i].y; if(min.z>vertexes[i].z) min.z=vertexes[i].z; if(max.x<vertexes[i].x) max.x=vertexes[i].x; if(max.y<vertexes[i].y) max.y=vertexes[i].y; if(max.z<vertexes[i].z) max.z=vertexes[i].z; } min.x=(min.x+max.x)/2; // compute center min.y=(min.y+max.y)/2; min.z=(min.z+max.z)/2; for(int i=0;i<vertcount;i++) // move to center { vertexes[i].x-=min.x; vertexes[i].y-=min.y; vertexes[i].z-=min.z; } } void computenormals() { if(!normals) normals=(Vertex *)malloc(sizeof(Vertex)*tricount); for(int i=0;i<tricount;i++) { normals[i]=Normalize(Normal(vertexes[triangles[i].a],vertexes[triangles[i].b],vertexes[triangles[i].c])); } } void draw() { glBegin(GL_TRIANGLES); for(int i=0;i<tricount;i++) { glNormal3f(normals[i].x,normals[i].y,normals[i].z); glVertex3f(vertexes[triangles[i].a].x,vertexes[triangles[i].a].y,vertexes[triangles[i].a].z); glVertex3f(vertexes[triangles[i].b].x,vertexes[triangles[i].b].y,vertexes[triangles[i].b].z); glVertex3f(vertexes[triangles[i].c].x,vertexes[triangles[i].c].y,vertexes[triangles[i].c].z); } glEnd(); } void drawnormals() { Vertex centre; glBegin(GL_LINES); for(int i=0;i<tricount;i++) { centre.x=(vertexes[triangles[i].a].x+vertexes[triangles[i].b].x+vertexes[triangles[i].c].x)/3; centre.y=(vertexes[triangles[i].a].y+vertexes[triangles[i].b].y+vertexes[triangles[i].c].y)/3; centre.z=(vertexes[triangles[i].a].z+vertexes[triangles[i].b].z+vertexes[triangles[i].c].z)/3; glVertex3f(centre.x,centre.y,centre.z); glVertex3f(centre.x-normals[i].x*10,centre.y-normals[i].y*10,centre.z-normals[i].z*10); } glEnd(); } void SaveToFile(char *fn) { if(!mapping) mapping=(MapCoord *)calloc(vertcount,sizeof(MapCoord)); FILE *f; f=fopen(fn,"w"); fprintf(f,"%i\n",vertcount); for(int i=0;i<vertcount;i++) fprintf(f,"%f %f %f %f %f\n",vertexes[i].x,vertexes[i].y,vertexes[i].z,mapping[i].u,mapping[i].v); fprintf(f,"%i\n",tricount); for(int i=0;i<tricount;i++) fprintf(f,"%i %i %i %f %f %f\n",triangles[i].a,triangles[i].b,triangles[i].c,normals[i].x,normals[i].y,normals[i].z); fclose(f); } }; #endif // MESH_H_INCLUDED
Конструктивні наїзди приймаються :).
Наступна стаття буде анымацыя? +)
У мене э класна книжка по морф-анымацыъ
danbst
Травень 28, 2009 at 10:20
Якщо буде. По моєму, перед всякими там морф-анімаціями треба буде вивчити кісткові, і квантерніони.
bunyk
Травень 28, 2009 at 12:15
книжка по всым анымацыям – выд найпростыших ы завантаження з Х файлыв – до морфынгу
danbst
Травень 28, 2009 at 16:39