Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Рендеринг тривимірної моделі

with 3 comments

Недавно, я розказував про розшифрування формату 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

Конструктивні наїзди приймаються :).

Advertisements

Written by bunyk

Травень 24, 2009 at 17:16

Оприлюднено в Графіка, Кодерство

Tagged with

Відповідей: 3

Subscribe to comments with RSS.

  1. Наступна стаття буде анымацыя? +)

    У мене э класна книжка по морф-анымацыъ

    danbst

    Травень 28, 2009 at 10:20

  2. Якщо буде. По моєму, перед всякими там морф-анімаціями треба буде вивчити кісткові, і квантерніони.

    bunyk

    Травень 28, 2009 at 12:15

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

    danbst

    Травень 28, 2009 at 16:39


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: