13.2 Загрузка, текстурирование и визуализация 3D моделей в OpenGL. Формат ASE.

Для создания программы загрузки 3D-моделей из формата ASE мы воспользуемся наработками оболочки программы, которые появились у нас после выполнения отрисовки моделей с помощью стандартных функция библиотеки GLUT (рис. 1).
Уроки OpenGL + C#: Окно программы Рисунок 1. Окно программы.
Обратите внимание на то, что мы должны создать в данном окне меню для отображения окна выбора файла, который в дальнейшем будет загружен (рис. 2).
Уроки OpenGL + C#: Меню загрузки файла 3D модели Рисунок 2. Меню загрузки файла 3D модели.
В остальном код оболочки программы остался неизменным за некоторыми исключениями, которые мы рассмотрим после того, как рассмотрим реализацию класса для загрузки 3D-модели.

Добавьте к проекту файл anModelLoader.cs. В нем будут реализованы классы, которые будут отвечать за загрузку, хранение и отрисовку 3D-модели.

Сейчас мы подробно его рассмотрим. Для начала посмотрите на рисунок 3. На нем представлено изображение 2-х полигонов, имеющих одно общее ребро.
Уроки OpenGL + C#: Пример полигонов Рисунок 3. Пример полигонов.
На рисунке прокомментированы две вершины, которые не однократно используются для описания (построения) отрезков, образующих полигоны. Не сложно догадаться о том, что описывая каждый полигон, из которых состоит трехмерная модель, мы получим огромную избыточность данных. Поэтому для хранения информации о геометрии трехмерного массива, как правило, используют два массива:
  • Первый массив содержит координаты всех вершин (каждая вершина описывается 3 координатами).
  • Второй массив содержит последовательности из номеров вершин, которые мы должны соединять, чтобы восстановить геометрию загружаемой 3D-модели.
Как видите, довольно просто. Теперь, если мы дополнительно вычислим (или загрузим из файла) координаты векторов нормалей для каждого отрисовываемого полигона, то мы сможем отрисовывать трехмерную модель.

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

Дальше, реализовав камеру для перемещения по трехмерной сцене или какие-либо другие опции просмотра данной модели, мы можем добиться высокого уровня интерактивности создаваемого приложения. Стоит отметить вопрос визуализации данной 3D-модели.

После загрузки модели геометрия будет загружена в массив объектов limb, каждый из элементов которого будет отвечать за один из логических элементов сцены. Т.е. если в трехмерной сцене было экспортировано два объекта - куб и 3D-модель чайника, то мы получим массив из 2-х экземпляров класса limb, один из которых будет описывать геометрию чайника, второй – геометрию куба.

После загрузки производится визуализация данной модели в дисплейный список. После этого нам достаточно будет вызывать данный дисплейный список для отрисовки геометрии, что сильно сократит нагрузку на графический адаптер. Объектно-ориентированное программирование позволит нам удобно загружать любое количество 3D-моделей, а также легко управлять перемещением данных 3D-моделей в сцене и их отрисовкой.

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

1. Класс LIMB:

/*http://esate.ru, Anvi*/

// класс LIMB отвечает за логические единицы 3D объектов в загружаемой сцене 
class LIMB
{

  // при инициализации мы должны указать количество вершин (vertex) и полигонов
  // (face) которые описывают геометрию подобъекта 
  public LIMB( int a, int b)
  { 
    if (temp[0] == 0)
      temp[0] = 1;

    // записываем количество вершин и полигонов 
    VandF[0] = a;
    VandF[1] = b;

    // выделяем память 
    memcompl();
  }

  public int Itog; // флаг успешности 

  // массивы для хранения данных (геометрии и текстурных координат) 
  public float[,] vert;
  public int[,] face;
  public float[,] t_vert;
  public int[,] t_face;

  // номер материала (текстуры) данного подобъекта 
  public int MaterialNom = -1;

  // временное хранение информации 
  public int[] VandF = new int[4];
  private int[] temp = new int[2];

  // флаг, говорящий о том, что модель использует текстуру 
  private bool ModelHasTexture = false ;

  // функция для определения значения флага (о наличии текстуры) 
  public bool NeedTexture()
  { 
    // возвращаем значение флага 
    return ModelHasTexture;
  }

  // массивы для текстурных координат 
  public void createTextureVertexMem( int a)
  { 
    VandF[2] = a;
    t_vert = new float[3, VandF[2]];
  }

  // привязка значений текстурных координат к полигонам 
  public void createTextureFaceMem( int b)
  { 
    VandF[3] = b;
    t_face = new int[3, VandF[3]];

    // отмечаем флаг о наличии текстуры 
    ModelHasTexture = true;
  }

  // память для геометрии 
  private void memcompl()
  { 
    vert = new float[3, VandF[0]];
    face = new int[3, VandF[1]];
  }

  // номер текстуры 
  public int GetTextureNom()
  { 
    return MaterialNom;
  }

};

// данный класс относился к немного другой реализации, будем считать его заготовкой реализации на будущее
public class Model_Prop
{
  public Model_Prop()
  {
    pos_abs[0] = 0;
    pos_abs[1] = 0;
    pos_abs[2] = 0; 

    maximum[0] = 0;
    maximum[1] = 0;
    maximum[2] = 0;
    minimum[0] = 0;
    minimum[1] = 0;
    minimum[2] = 0;
    rotating_angles[0] = 0;
    rotating_angles[1] = 0;
    rotating_angles[2] = 0;
  }
  
  public float[] pos_abs = new float[3];
  public float[] maximum = new float[3];
  public float[] minimum = new float[3];
  public float[] rotating_angles = new float[3];

};


Методы использования библиотеки DevIL мы уже изучали. Теперь мы должны немного изменить реализацию таким образом, чтобы завернуть ее в класс, который позволит быстро загружать и использовать текстуру прямо при чтении файла с геометрией сцены.

/*http://esate.ru, Anvi*/

// класс для работы с текстурами 
public class TexturesForObjects
{

  public TexturesForObjects()
  { 

  }

  // имя текстуры 
  private string texture_name = "";
  // ее ID 
  private int imageId = 0;

  // идентификатор текстуры в памяти openGL 
  private uint mGlTextureObject = 0;

  // получение этого идентификатора 
  public uint GetTextureObj()
  { 
    return mGlTextureObject;
  }

  // загрузка текстуры 
  public void LoadTextureForModel( string FileName)
  { 
    // запоминаем имя файла 
    texture_name = FileName;
    // создаем изображение с идентификатором imageId 
    Il.ilGenImages(1, out imageId);
    // делаем изображение текущим 
    Il.ilBindImage(imageId);

    string url = "";
    // получаем адрес текущей директории 
    url = Directory.GetCurrentDirectory();
    url += "";
    // добавляем имя текстуры 
    url += texture_name;

    // если загрузка удалась 
    if ( Il.ilLoadImage(url))
    {

      // если загрузка прошла успешно 
      // сохраняем размеры изображения 
      int width = Il.ilGetInteger( Il.IL_IMAGE_WIDTH);
      int height = Il.ilGetInteger( Il.IL_IMAGE_HEIGHT);

      // определяем число бит на пиксель 
      int bitspp = Il.ilGetInteger( Il.IL_IMAGE_BITS_PER_PIXEL);

      switch (bitspp) // в зависимости от полученного результата 
      {
        // создаем текстуру используя режим GL_RGB или GL_RGBA 
        case 24:
        mGlTextureObject = MakeGlTexture( Gl.GL_RGB, Il.ilGetData(), width, height);
        break ;
        case 32:
        mGlTextureObject = MakeGlTexture( Gl.GL_RGBA, Il.ilGetData(), width, height);
        break ;
      }

      // очищаем память 
      Il.ilDeleteImages(1, ref imageId);
    }

  }

  // создание текстуры в памяти openGL 
  private static uint MakeGlTexture( int Format, IntPtr pixels, int w, int h)
  { 
  
    // идентификатор текстурного объекта 
    uint texObject;
    // генерируем текстурный объект 
    Gl.glGenTextures(1, out texObject);
    // устанавливаем режим упаковки пикселей 
    Gl.glPixelStorei( Gl.GL_UNPACK_ALIGNMENT, 1);
    // создаем привязку к только что созданной текстуре 
    Gl.glBindTexture( Gl.GL_TEXTURE_2D, texObject);

    // устанавливаем режим фильтрации и повторения текстуры 
    Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
    Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);
    Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
    Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
    Gl.glTexEnvf( Gl.GL_TEXTURE_ENV, Gl.GL_TEXTURE_ENV_MODE, Gl.GL_REPLACE);

    // создаем RGB или RGBA текстуру 
    switch (Format)
    {

      case Gl.GL_RGB:
      Gl.glTexImage2D( Gl.GL_TEXTURE_2D, 0, Gl.GL_RGB, w, h, 0, Gl.GL_RGB, Gl.GL_UNSIGNED_BYTE, pixels);
      break ;
      case Gl.GL_RGBA:
      Gl.glTexImage2D( Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, w, h, 0, Gl.GL_RGBA, Gl.GL_UNSIGNED_BYTE, pixels);
      break ;

    }

    // возвращаем идентификатор текстурного объекта 
    return texObject;

  }


}


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

/*http://esate.ru, Anvi*/

*3DSMAX_ASCIIEXPORT 200
*COMMENT "AsciiExport Version 2,00 - Mon Jan 18 01:42:41 2010"
*SCENE {


*SCENE_FILENAME ""
*SCENE_FIRSTFRAME 0
*SCENE_LASTFRAME 100
*SCENE_FRAMESPEED 30
*SCENE_TICKSPERFRAME 160
*SCENE_BACKGROUND_STATIC 0.0000 0.0000 0.0000
*SCENE_AMBIENT_STATIC 0.0000 0.0000 0.0000

}
*MATERIAL_LIST { 
*MATERIAL_COUNT 0

}
*GEOMOBJECT { 
*NODE_NAME "Box01"
*NODE_TM { 
*NODE_NAME "Box01"
*INHERIT_POS 0 0 0
*INHERIT_ROT 0 0 0
*INHERIT_SCL 0 0 0
*TM_ROW0 0.3309 0.0000 0.0000
*TM_ROW1 0.0000 0.3309 0.0000
*TM_ROW2 0.0000 0.0000 0.3309
*TM_ROW3 -21.0978 -6.6046 7.9985
*TM_POS -21.0978 -6.6046 7.9985
*TM_ROTAXIS 0.0000 0.0000 0.0000
*TM_ROTANGLE 0.0000
*TM_SCALE 0.3309 0.3309 0.3309
*TM_SCALEAXIS 0.0000 0.0000 0.0000
*TM_SCALEAXISANG 0.0000

}
*MESH { 
*TIMEVALUE 0
*MESH_NUMVERTEX 8
*MESH_NUMFACES 12
*MESH_VERTEX_LIST { 
*MESH_VERTEX 0 -25.6486 -11.3866 7.9985
*MESH_VERTEX 1 -16.5471 -11.3866 7.9985
*MESH_VERTEX 2 -25.6486 -1.8227 7.9985
*MESH_VERTEX 3 -16.5471 -1.8227 7.9985
*MESH_VERTEX 4 -25.6486 -11.3866 15.3495
*MESH_VERTEX 5 -16.5471 -11.3866 15.3495
*MESH_VERTEX 6 -25.6486 -1.8227 15.3495
*MESH_VERTEX 7 -16.5471 -1.8227 15.3495

}
*MESH_FACE_LIST { 
*MESH_FACE 0: A: 0 B: 2 C: 3 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 2 *MESH_MTLID 1
*MESH_FACE 1: A: 3 B: 1 C: 0 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 2 *MESH_MTLID 1
*MESH_FACE 2: A: 4 B: 5 C: 7 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 3 *MESH_MTLID 0
*MESH_FACE 3: A: 7 B: 6 C: 4 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 3 *MESH_MTLID 0
*MESH_FACE 4: A: 0 B: 1 C: 5 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 4 *MESH_MTLID 4
*MESH_FACE 5: A: 5 B: 4 C: 0 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 4 *MESH_MTLID 4
*MESH_FACE 6: A: 1 B: 3 C: 7 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 5 *MESH_MTLID 3
*MESH_FACE 7: A: 7 B: 5 C: 1 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 5 *MESH_MTLID 3
*MESH_FACE 8: A: 3 B: 2 C: 6 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 6 *MESH_MTLID 5
*MESH_FACE 9: A: 6 B: 7 C: 3 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 6 *MESH_MTLID 5
*MESH_FACE 10: A: 2 B: 0 C: 4 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 7 *MESH_MTLID 2
*MESH_FACE 11: A: 4 B: 6 C: 2 AB: 1 BC: 1 CA: 0 *MESH_SMOOTHING 7 *MESH_MTLID 2

}
*MESH_NUMTVERTEX 12
*MESH_TVERTLIST { 
*MESH_TVERT 0 0.0000 0.0000 0.0000
*MESH_TVERT 1 1.0000 0.0000 0.0000
*MESH_TVERT 2 0.0000 1.0000 0.0000
*MESH_TVERT 3 1.0000 1.0000 0.0000
*MESH_TVERT 4 0.0000 0.0000 0.0000
*MESH_TVERT 5 1.0000 0.0000 0.0000
*MESH_TVERT 6 0.0000 1.0000 0.0000
*MESH_TVERT 7 1.0000 1.0000 0.0000
*MESH_TVERT 8 0.0000 0.0000 0.0000
*MESH_TVERT 9 1.0000 0.0000 0.0000
*MESH_TVERT 10 0.0000 1.0000 0.0000
*MESH_TVERT 11 1.0000 1.0000 0.0000
}
*MESH_NUMTVFACES 12
*MESH_TFACELIST { 
*MESH_TFACE 0 9 11 10
*MESH_TFACE 1 10 8 9
*MESH_TFACE 2 8 9 11
*MESH_TFACE 3 11 10 8
*MESH_TFACE 4 4 5 7
*MESH_TFACE 5 7 6 4
*MESH_TFACE 6 0 1 3
*MESH_TFACE 7 3 2 0
*MESH_TFACE 8 4 5 7
*MESH_TFACE 9 7 6 4
*MESH_TFACE 10 0 1 3
*MESH_TFACE 11 3 2 0

}

}
*PROP_MOTIONBLUR 0
*PROP_CASTSHADOW 1
*PROP_RECVSHADOW 1



Как видно из кода, файл представляет собой управляющие конструкции. Ключевые слова, указывающие на новую управляющую конструкцию, отмечены знаком «*» в начале слова.

Алгоритм чтения файла и организация файла модели в памяти программы будут выглядеть следующим образом:

1. Начинается построчное чтение файла (вся необходимая информация для любой управляющей конструкции будет находиться в рамках одной строки, поэтому построчное чтение нам подходит)

2. Отсечение первого слова в строке.

3. Проверка первого символа в полученном первом слове – если оно равно «*», то мы имеем управляющее слово.

4. На основе управляющего слова мы проверяем, что это за управляющее слово, и если оно относится к тем, которые необходимы нам (описывают геометрию, текстурные координаты), то мы обрабатываем данную строку, отрезая первые слова и затем переводя их из строкового представления к числовому.

Как видите, все довольно просто. Теперь немного опишем те управляющие слова, которые нас интересуют:

*MATERIAL_COUNT – количество текстур, используемое в данной 3D-сцене.
Если значение этого параметра не равно 0, то дальше будет идти описание каждого материала. Среди параметров ambient, diffuse, specular, которые отражают свойства материала (и обработку которых мы не рассматривали в рамках данной программы) в блоке описания материала будет также указана информация о текстуре.

*MATERIAL – начало блока описания материала. Далее следует указание номера материала.

Пример блока описания материала:

/*http://esate.ru, Anvi*/


*MATERIAL_LIST {

*MATERIAL_COUNT 1
*MATERIAL 0 { 
*MATERIAL_NAME "01 - Default"
*MATERIAL_CLASS "Standard"
*MATERIAL_AMBIENT 0.5882 0.5882 0.5882
*MATERIAL_DIFFUSE 0.5882 0.5882 0.5882
*MATERIAL_SPECULAR 0.9000 0.9000 0.9000
*MATERIAL_SHINE 0.1000
*MATERIAL_SHINESTRENGTH 0.0000
*MATERIAL_TRANSPARENCY 0.0000
*MATERIAL_WIRESIZE 1.0000
*MATERIAL_SHADING Blinn
*MATERIAL_XP_FALLOFF 0.0000
*MATERIAL_SELFILLUM 0.0000
*MATERIAL_FALLOFF In
*MATERIAL_XP_TYPE Filter

*MAP_DIFFUSE { 
*MAP_NAME "Map #1"
*MAP_CLASS "Bitmap"
*MAP_SUBNO 1
*MAP_AMOUNT 1.0000
*BITMAP "C:UsersanviDesktop13-3.png"
*MAP_TYPE Screen
*UVW_U_OFFSET 0.0000
*UVW_V_OFFSET 0.0000
*UVW_U_TILING 1.0000
*UVW_V_TILING 1.0000
*UVW_ANGLE 0.0000
*UVW_BLUR 1.0000
*UVW_BLUR_OFFSET 0.0000
*UVW_NOUSE_AMT 1.0000
*UVW_NOISE_SIZE 1.0000
*UVW_NOISE_LEVEL 1
*UVW_NOISE_PHASE 0.0000
*BITMAP_FILTER Pyramidal

}

}

}



*BITMAP – адрес текстуры для данного материала.

*MATERIAL_REF – указание на то, какой номер материала назначен данному объекту.

Теперь рассмотрим описание геометрии и текстурных координат.

*GEOMOBJECT – указывает на новый геометрический подобъект в загружаемой из файла трехмерной сцене. Этот блок также описывает довольно большое количество информации – имя объекта, его начальные координаты, ориентация и т.д.

*MESH_NUMVERTEX – указывает на количество вершин, которые будут описывать геометрию данного 3D-объекта (подобъекта). Мы будем использовать это значение (и значение, описывающие количество полигонов) для выделения памяти при создании экземпляра, описывающего геометрию подобъекта.

*MESH_NUMFACES – указывает количество полигонов, в данном геометрическом подобъекте.

*MESH_NUMTVERTEX, *MESH_NUMTVFACES – описание текстурных координат и их привязка к полигонам.

*MESH_VERTEX, *MESH_FACE, *MESH_TVERT, *MESH_TFACE – непосредственное описание вершин и полигонов (геометрии и текстурных координат). Самые часто встречаемые ключевые слова.

За данными ключевыми словами следует ряд параметров. В случае вершин - это номер вершины и три ее координаты. В случае описания полигонов – это номер полигона (после которого стоит знак «:», который необходимо отрезать) и далее номера вершин, координаты которых должны быть использованы для построения полигона. Обратите внимание на то, что часть информации из данной строки мы не используем.

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

/*http://esate.ru, Anvi*/

// класс, выполняющий загрузку 3D модели 
public class anModelLoader
{

  public anModelLoader()
  {

  }

  // имя файла 
  public string FName = "";

  // загружен ли (флаг) 
  private bool isLoad = false ;
  // счетчик подобъектов 
  private int count_limbs;
  // переменная для хранения номера текстуры 
  private int mat_nom = 0;

  // номер дисплейного списка с данной моделью 
  private int thisList = 0;

  // данная переменная будет указывать на количество
  // прочитанных символов в строке при чтении информации из файла 
  private int GlobalStringFrom = 0;

  // массив подобъектов 
  LIMB[] limbs = null;

  // массив для хранения текстур 
  TexturesForObjects[] text_objects = null;

  // описание ориентации модели 
  Model_Prop coord = new Model_Prop();
  …



Функция загрузки 3D модели, как мы уже говорили, это построчное чтение файла и поиск управляющих слов, а также последующее внесение описываемых ими данных в объекты описания геометрии и создание и загрузка текстурных объектов в память OpenGL:

/*http://esate.ru, Anvi*/



// загрузка модели 
public int LoadModel(string FileName)
{

  // модель может содержать до 256 подобъектов 
  limbs = new LIMB[256];
  // счетчик скинут 
  int limb_ = -1;

  // имя файла 
  FName = FileName;

  // начинаем чтение файла 
  StreamReader sw = new StreamReader(FileName, Encoding.Default);

  // временные буферы 
  string a_buff = "";
  string b_buff = "";
  string c_buff = "";

  // счетчики вершин и полигонов 
  int ver = 0, fac = 0;

  // если строка успешно прочитана 
  while ((a_buff = sw.ReadLine()) != null)
  { 

    // получаем первое слово 
    b_buff = GetFirstWord(a_buff, 0);
    
    if (b_buff[0] == '*') // определяем, является ли первый символ звездочкой 
    {
      switch (b_buff) // если да, то проверяем какое управляющее
      // слово содержится в первом прочитанном слове 
      {

        case "*MATERIAL_COUNT": // счетчик материалов 
        {
          // получаем первое слово от символа указанного в GlobalStringFrom 
          c_buff = GetFirstWord(a_buff, GlobalStringFrom);
          int mat = System.Convert.ToInt32(c_buff);

          // создаем объект для текстуры в памяти 
          text_objects = new TexturesForObjects[mat];
          continue;
        }

        case "*MATERIAL_REF": // номер текстуры 
        {

          // записываем для текущего подобъекта номер текстуры 
          c_buff = GetFirstWord(a_buff, GlobalStringFrom);
          int mat_ref = System.Convert.ToInt32(c_buff);

          // устанавливаем номер материала, соответствующий данной модели. 
          limbs[limb_].SetMaterialNom(mat_ref);
          continue;
        }

        case "*MATERIAL": // указание на материал 
        {
          c_buff = GetFirstWord(a_buff, GlobalStringFrom);
          mat_nom = System.Convert.ToInt32(c_buff);
          continue;
        }

        case "*GEOMOBJECT": // начинается описание геометрии подобъекта 
        {
          limb_++; // записываем в счетчик подобъектов 
          continue;
        }

        case "*MESH_NUMVERTEX": // количество вершин в подобъекте 
        {
          c_buff = GetFirstWord(a_buff, GlobalStringFrom);
          ver = System.Convert.ToInt32(c_buff);
          continue;
        }

        case "*BITMAP": // имя текстуры 
        {

          c_buff = ""; // обнуляем временный буфер 
          for ( int ax = GlobalStringFrom + 2; ax < a_buff.Length - 1; ax++)
            c_buff += a_buff[ax]; // считываем имя текстуры 
            
          text_objects[mat_nom] = new TexturesForObjects(); // новый объект для текстуры 
          text_objects[mat_nom].LoadTextureForModel(c_buff); // загружаем текстуру 
          continue;

        }

        case "*MESH_NUMTVERTEX": // количество текстурных координат
        // данное слово говорит о наличии текстурных координат,
        // следовательно, мы должны выделить память для них 
        {
        c_buff = GetFirstWord(a_buff, GlobalStringFrom);
        
          if (limbs[limb_] != null)
          {
            limbs[limb_].createTextureVertexMem( System.Convert.ToInt32(c_buff));
          }
  
          continue;
        }

        case "*MESH_NUMTVFACES": // память для текстурных координат (faces) 
        {

          c_buff = GetFirstWord(a_buff, GlobalStringFrom);

          if (limbs[limb_] != null)
          {
            // выделяем память для текстурных координат 
            limbs[limb_].createTextureFaceMem( System.Convert.ToInt32(c_buff));
          }
          continue;
        }

        case "*MESH_NUMFACES": // количество полигонов в подобъекте 
        {
          c_buff = GetFirstWord(a_buff, GlobalStringFrom);
          fac = System.Convert.ToInt32(c_buff);

          // если было объявляющее слово *GEOMOBJECT
          // (гарантия выполнения условия limb_ > -1) и были указаны количество вершин 
          if (limb_ > -1 && ver > -1 && fac > -1)
          {
            // создаем новый подобъект в памяти 
            limbs[limb_] = new LIMB(ver, fac);
          }
          else
          {
            // иначе завершаем неудачей 
            return -1;
          }
          continue;
        }

        case "*MESH_VERTEX": // информация о вершине 
        {

          // подобъект создан в памяти 
          if (limb_ == -1)
            return -2;
          if (limbs[limb_] == null)
            return -3;

          string a1 = "", a2 = "", a3 = "", a4 = "";

          // получаем информацию о координатах и номере вершины 
          // (получаем все слова в строке) 
          a1 = GetFirstWord(a_buff, GlobalStringFrom);
          a2 = GetFirstWord(a_buff, GlobalStringFrom);
          a3 = GetFirstWord(a_buff, GlobalStringFrom);
          a4 = GetFirstWord(a_buff, GlobalStringFrom);

          // преобразовываем в целое число 
          int NomVertex = System.Convert.ToInt32(a1);

          // заменяем точки в представлении числа с плавающей точкой
          // на запятые, чтобы правильно выполнилась функция 
          // преобразования строки в дробное число 
          a2 = a2.Replace('.', ',');
          a3 = a3.Replace('.', ',');
          a4 = a4.Replace('.', ',');

          // записываем информацию о вершине 
          limbs[limb_].vert[0, NomVertex] = (float) System.Convert.ToDouble(a2); // x 
          limbs[limb_].vert[1, NomVertex] = (float) System.Convert.ToDouble(a3); // y 
          limbs[limb_].vert[2, NomVertex] = (float) System.Convert.ToDouble(a4); // z 

          continue;

        }

        case "*MESH_FACE": // информация о полигоне 
        {

          // подобъект создан в памяти 
          if (limb_ == -1)
          return -2;
          if (limbs[limb_] == null)
          return -3;

          // временные переменные 
          string a1 = "", a2 = "", a3 = "", a4 = "", a5 = "", a6 = "", a7 = "";

          // получаем все слова в строке 
          a1 = GetFirstWord(a_buff, GlobalStringFrom);
          a2 = GetFirstWord(a_buff, GlobalStringFrom);
          a3 = GetFirstWord(a_buff, GlobalStringFrom);
          a4 = GetFirstWord(a_buff, GlobalStringFrom);
          a5 = GetFirstWord(a_buff, GlobalStringFrom);
          a6 = GetFirstWord(a_buff, GlobalStringFrom);
          a7 = GetFirstWord(a_buff, GlobalStringFrom);

          // получаем номер полигона из первого слова в строке, заменив
          // последний символ ':' после номера на флаг окончания строки. 
          int NomFace = System.Convert.ToInt32(a1.Replace(':', ''));

          // записываем номера вершин, которые нас интересуют 
          limbs[limb_].face[0, NomFace] = System.Convert.ToInt32(a3);
          limbs[limb_].face[1, NomFace] = System.Convert.ToInt32(a5);
          limbs[limb_].face[2, NomFace] = System.Convert.ToInt32(a7);

          continue;

        }

        // текстурные координаты 
        case "*MESH_TVERT":
        {

          // подобъект создан в памяти 
          if (limb_ == -1)
          return -2;
          if (limbs[limb_] == null)
          return -3;

          // временные переменные 
          string a1 = "", a2 = "", a3 = "", a4 = "";

          // получаем все слова в строке 
          a1 = GetFirstWord(a_buff, GlobalStringFrom);
          a2 = GetFirstWord(a_buff, GlobalStringFrom);
          a3 = GetFirstWord(a_buff, GlobalStringFrom);
          a4 = GetFirstWord(a_buff, GlobalStringFrom);

          // преобразуем первое слово в номер вершины 
          int NomVertex = System.Convert.ToInt32(a1);

          // заменяем точки в представлении числа с плавающей точкой,
          // на запятые, чтобы правильно выполнилась функция 
          // преобразования строки в дробное число 
          a2 = a2.Replace('.', ',');
          a3 = a3.Replace('.', ',');
          a4 = a4.Replace('.', ',');

          // записываем значение вершины 
          limbs[limb_].t_vert[0, NomVertex] = (float) System.Convert.ToDouble(a2); // x 
          limbs[limb_].t_vert[1, NomVertex] = (float) System.Convert.ToDouble(a3); // y 
          limbs[limb_].t_vert[2, NomVertex] = (float) System.Convert.ToDouble(a4); // z 

          continue;

        }

        // привязка текстурных координат к полигонам 
        case "*MESH_TFACE":
        {

          // подобъект создан в памяти 
          if (limb_ == -1)
          return -2;
          if (limbs[limb_] == null)
          return -3;

          // временные переменные 
          string a1 = "", a2 = "", a3 = "", a4 = "";

          // получаем все слова в строке 
          a1 = GetFirstWord(a_buff, GlobalStringFrom);
          a2 = GetFirstWord(a_buff, GlobalStringFrom);
          a3 = GetFirstWord(a_buff, GlobalStringFrom);
          a4 = GetFirstWord(a_buff, GlobalStringFrom);

          // преобразуем первое слово в номер полигона 
          int NomFace = System.Convert.ToInt32(a1);

          // записываем номера вершин, которые описывают полигон 
          limbs[limb_].t_face[0, NomFace] = System.Convert.ToInt32(a2);
          limbs[limb_].t_face[1, NomFace] = System.Convert.ToInt32(a3);
          limbs[limb_].t_face[2, NomFace] = System.Convert.ToInt32(a4);

          continue;

        }

      }

    }

  }
  
// пересохраняем количество полигонов 
count_limbs = limb_;


// получаем ID для создаваемого дисплейного списка 
int nom_l = Gl.glGenLists(1);
thisList = nom_l;
// генерируем новый дисплейный список 
Gl.glNewList(nom_l, Gl.GL_COMPILE);
// отрисовываем геометрию 
CreateList();
// завершаем дисплейный список 
Gl.glEndList();

// загрузка завершена 
isLoad = true ;

return 0;

}


Как было видно из кода, последний этап - это генерация дисплейного списка. Мы сгенерировали его номер, затем начали создание дисплейного списка и вызвали функцию CreateList();. В данной функции производилась визуализация полученной геометрии 3D-модели с учетом установленных текстур, текстурных координат.

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

Нет доступа к просмотру комментариев.

^