Меш (Mesh)

Mesh - в прямом переводе - сетчатая структура используется следующим образом:
1) один из поддерживаемых в DirectX форматов - X-File с расширением .x.
2) Direct3D-Класс из Microsoft.DirectX.Direct3DX, который загружает и отрисовывает такие .x-Файлы.

Простые Меши

Direct3D работает с несколькими простейшими стандартными 3D-фигурами в своем Mesh-Классе, из которых можно составлять любые фигуры.
Их Вертексы типа CustomVertex.PositionNormal, могут быть освещены, но не содержат информации о цвете и текстурных координатах и поэтому не могут быть текстурированными. Эти Вертексы используют Material-свойства Device.

Пример:

static Mesh myPolygon = Mesh.Polygon ( device, 0.3f, 8 ); //создает многоугольник со стороной 
// равной 0.3f и количеством
// симметричных верши - 8

static
Mesh myBox = Mesh.Box ( device, 0.5f, 0.5f, 0.5f ); // параллелепипед со сторонами
// xSize = 0.5f, ySize = 0.5f, zSize = 0.5f

static Mesh mySphere = Mesh.Sphere ( device, 0.5f, 20, 20 ); //радиус 0.5f, слои вокруг оси,
// ярусы вдоль оси

static Mesh myTorus = Mesh.Torus ( device, 0.2f, 0.4f, 20, 20 );//внутренний и внешний радиусы,
// слои и ярусы

static Mesh myCylinder = Mesh.Cylinder( device, 0.5f, 0.2f, 0.8f, 20, 20 ); //радиусы,
//высота, слои+ярусы
static Mesh myTeapot = Mesh.Teapot ( device ); // классический чайник

static Mesh myText = Mesh.TextFromFont( device, new System.Drawing.Font(
FontFamily.GenericSerif, 12 )
, text, 0.01f, 0.25f );
//строка, сглаживание краев, толщина
// а теперь отрисуем все это

device.BeginScene();
myPolygon .DrawSubset( 0 );
myBox .DrawSubset( 0 );
mySphere .DrawSubset( 0 );
myTorus .DrawSubset( 0 );
myCylinder.DrawSubset( 0 );
myTeapot .DrawSubset( 0 );
myText .DrawSubset( 0 );
device.EndScene();

X-Files

X-Файловый формат разработан в Microsoft и стал, также как и BMP-Формат для растровых картинок, с течением времени универсальным и популярным. Существует в двух видах a) компактный, но нечитаемый бинарный формат и b) как редактируемый текстовый формат.
Описание формата можно найти тут: paulbourke.net/dataformats/.
Практически их любого редактора 3D-Моделирования можно экспортировать X-Files:
Maya 5 und Maya 6 Plug-Ins после установки DirectX SDK: C:\DXSDK\Utilities\Bin\plugins\Maya
Adobe Photoshop Plug-In после установки DirectX SDK: C:\DXSDK\Utilities\Bin\plugins\Photoshop
Autodesk 3ds Max Plug-In : www.andytather.co.uk/Panda/directxmax_downloads.aspx
AC3D : www.inivis.com/
LightWave 3D siehe: https://www.lightwave3d.com/
MeshX : www.spinnerbaker.com/meshx.htm
MilkShape 3D : http://www.milkshape3d.com/

X-File Коллекция: www.3dcafe.com
Коллекция полезных статей : sites.google.com/site/craigandera/craigs-stuff/directx-home

Мы можем сами написать X-File в текстовом формате - это муторно, но поучительно.

Пример одного .x-Файла :

xof 0302txt 0064 //mandatory X-file header 
Mesh { 
//VertexBuffer
9; //vertex count
   -1.0; 1.0; 0.0;,//p0 
0.0; 1.0; 0.0;, //p1
0.0; 0.0; 0.0;, //p2
-1.0; 0.0; 0.0;,//p3
0.0;-1.0; 0.0;, //p4
-1.0;-1.0; 0.0;,//p5
0.0; 1.0; 1.0;, //p6
0.0; 0.0; 1.0;, //p7
0.0;-1.0; 1.0;; //p8
//IndexBuffer
8; //face count
3; 0, 1, 2;, //triangle0
3; 0, 2, 3;, //triangle1
3; 3, 2, 4;, //triangle2
3; 3, 4, 5;, //triangle3
3; 1, 6, 7;, //triangle4
3; 1, 7, 2;, //triangle5
3; 2, 7, 8;, //triangle6
3; 2, 8, 4;; //triangle7

MeshVertexColors {
      9; //vertex count 
0; 1.0; 0.0; 0.0; 0;;, //red
1; 0.0; 1.0; 0.0; 0;;, //green
2; 0.0; 0.0; 1.0; 0;;, //blue
3; 1.0; 0.6; 0.0; 0;;, //orange
4; 1.0; 1.0; 0.0; 0;;, //yellow
5; 0.6; 0.2; 0.2; 0;;, //brown
6; 0.0; 0.0; 0.0; 0;;, //black
7; 0.0; 1.0; 1.0; 0;;, //cyan
8; 1.0; 0.0; 1.0; 0;;; //magenta
}

MeshMaterialList { //void but mandatory
1;1;0;;
Material { 0;0;0;0;;0;0;0;0;;0;0;0;; }
}
} //end of Mesh
     




Скопируйте этот текст в текстовой файл  C:\temp\mesh.x.
Обратите внимание:
1) .x-Файлы должны начинаться сточкой xof 0302txt 0064.
2) Затем .x-Файлы содержат обычно так называемые Templates = синтаксический описатель блоков. Эти Templates необязательные части файла и мы их отбросили.
3) Блок Mesh содержит Vertex- и Index-Buffer и два подблока MeshVertexColors и MeshMaterialList, где последний обязателен для присутствия, но в нашем случае не имеет смысла.
4) за знаком // начинаются комментарии, как в обычном коде.
5) Каждый блок начинается с целого числа  = количество следующих элементов в данном блоке.
6) Элементы отделяются запятой, строки отделяются точкой с запятой. После последнего элемента следует дополнительная точка с запятой.
7) Каждый элемент IndexBuffer-а использует index count (в примере: 3; в левой колонке).
8) Каждый элемент MeshVertexColors-блок использует вертексный номер (в примере: 0; ... 8; в левой колонке).
9) Компоненты цвета R, G, B и прозрачность кодируются значениями от 0.0 = отсутствие и 1.0 = полное присутствие.
10) Написание 0.0 и 0 для типа float одинаковы по значению, в отличии от 1.0 и 1.
11) Маленькие синтаксические ошибки сделают Файл нерабочим, и эти ошибки тяжело найти, поэтому аккуратное структурированное написание крайне важно.

Загрузка Mesh-а

C#-Programm mesh1:

//Form1.cs ************************************************************************** 
//Add References: Microsoft.DirectX, Microsoft.Direct3D,
// Microsoft.Direct3DX, Vers. >= 1.0.2902.0

using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
public class Form1 : Form {
[STAThread] static void Main() {
Application.Run( new Form1() );
}

static Device device;
static Mesh mymesh;
static float fAngle;
PresentParameters presentParams;
Timer myTimer = new Timer();

public Form1() {
myTimer.Tick += new EventHandler( OnTimer );
myTimer.Interval = 1;
ClientSize = new Size( 400, 300 );
}

protected override void OnResize( System.EventArgs e ) {
myTimer.Stop();
try {
presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.EnableAutoDepthStencil = true;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
if ( device != null ) device.Dispose();
device = new Device( 0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing, presentParams );
if ( mymesh != null ) mymesh.Dispose();
try {
mymesh = Mesh.FromFile( @"C:\temp\mesh.x", MeshFlags.SystemMemory, device );
} catch {
MessageBox.Show( @"Missing or Invalid: C:\temp\mesh.x" );
return;
}
device.Transform.View = Matrix.LookAtLH( new Vector3( 0f,0f,-4f ),
new Vector3( 0f,0f,0f ),
new Vector3( 0f,1f,0f ) );
device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI/4, 1f, 1f, 10f );
device.RenderState.CullMode = Cull.None;
device.RenderState.Ambient = Color.White;
device.RenderState.AmbientMaterialSource = ColorSource.Color1;
myTimer.Start();
} catch (DirectXException ex) {
MessageBox.Show( ex.ToString() );
return;
}
}

protected static void OnTimer( Object myObject, EventArgs myEventArgs ) {
if (device == null) return;
device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.Blue, 1f, 0 );
device.Transform.World = Matrix.RotationY( fAngle += 0.01f );

device.BeginScene();
mymesh.DrawSubset(0);
device.EndScene();
device.Present();
}
}

Данная программа не создает Вертексов, Индексов и Цветов, а берет все это из .x-Файла C:\temp\mesh.x.
Вы должны подключить соответствующие библиотеки Microsoft.DirectX, Microsoft.DirectX.Direct3D и Microsoft.DirectX.Direct3DX.

ExtendedMaterials

a) В MeshMaterialList можно вставить в Material-блок какую-нибудь текстуру. В этом случае .x-Файл должен содержать дополнительные текстурные координаты.

MeshMaterialList {
  1;1;0;;
  Material {
     1.0;1.0;1.0;0;;0;0;0;0;;0;0;0;;
     TextureFilename { "C:\\temp\\bmp1.bmp"; }
  }
}

MeshTextureCoords {
 4;
 0.0, 0.0; //left  upper corner
 1.0, 0.0; //rigth upper corner
 0.0, 1.0; //left  lower corner
 1.0, 1.0;;//right lower corner
}

б)  В блоке MeshMaterialList могут содержаться два и более Material-блока. В этом случае ExtendedMaterials  должны загружаться по-одному, в static Material[]- и static Texture[]-Массивы копироваться и отрисовываться.

Пример с 4-мя четырехугольниками и 2-мя ExtendedMaterials:
Если вы хотите повторить пример, тогда загрузите эти исходные картинки в директорию C:\temp:


C:\temp\bmp1.bmp


C:\temp\bmp2.bmp
     
von mesh.x geliefertes Ergebnis
     

Измененный х-файл:

xof 0302txt 0064 //mandatory X-file header

Mesh {
  
//VertexBuffer
   9; //vertex count
   -1.0; 1.0; 0.0;, //p0
   0.0; 1.0; 0.0;, //p1
   0.0; 0.0; 0.0;, //p2
   -1.0; 0.0; 0.0;, //p3
   0.0;-1.0; 0.0;, //p4
   -1.0;-1.0; 0.0;, //p5
   0.0; 1.0; 1.0;, //p6
   0.0; 0.0; 1.0;, //p7
   0.0;-1.0; 1.0;; //p8
  
   //IndexBuffer
   4; //face count
   4; 0, 1, 2, 3;, //quad0
  
4; 3, 2, 4, 5;, //quad1
   4; 1, 6, 7, 2;, //quad2
   4; 2, 7, 8, 4;; //quad3
  
   MeshTextureCoords { //each pi needs a ti
      9;
      0.0, 0.0; //t0 → upper left
      1.0, 0.0; //t1 → upper rigth
      1.0, 1.0; //t2 → lower right
      0.0, 1.0; //t3 → lower left
      1.0, 0.0; //t4 = t1 → mirror down
      0.0, 0.0; //t5 = t0 → mirror down
      0.0, 0.0; //t6 = t0 → mirror right
      0.0, 1.0; //t7 = t3 → mirror right
      0.0, 0.0;;//t8 = t0 → mirror down and right
   } MeshMaterialList {
      2;4;0,1,1,0;; //2 mats for 4 faces: mat 0 for faces 0,3,
                    //mat 1 for faces 1,2
      Material { 1.0;1.0;1.0;0;;0;0;0;0;;0;0;0;;
         TextureFilename { "C:\\temp\\bmp1.bmp"; }
      }
      Material { 1.0;1.0;1.0;0;;0;0;0;0;;0;0;0;;
         TextureFilename { "C:\\temp\\bmp2.bmp"; }
      }
   }
}
//end of Mesh file C:\temp\mesh.x *********************************************************

//C#-Program: Form1.cs ********************************************************************
//Add References: Microsoft.DirectX, Microsoft.Direct3D, Microsoft.Direct3DX,
//Vers. >= 1.0.2902.0

using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
public class Form1 : Form {
   [STAThread] static void Main() {
      Application.Run( new Form1() );
   }
  
   static Device device;
   static Mesh mymesh;
   static float fAngle;
   ExtendedMaterial[] exmat; //new !
   static Material [] mat; //new !
   static Texture [] tex; //new !
   PresentParameters presentParams; 
   
   Timer myTimer = new Timer();
  
   public Form1() {
      Text = "mesh1";
      myTimer.Tick += new EventHandler( OnTimer );
      myTimer.Interval = 1;
      ClientSize = new Size( 400, 300 );
   }
  
   protected override void OnResize( System.EventArgs e ) {
      myTimer.Stop();
      try {
         presentParams = new PresentParameters();
         presentParams.Windowed = true;
         presentParams.SwapEffect = SwapEffect.Discard;
         presentParams.EnableAutoDepthStencil = true;
         presentParams.AutoDepthStencilFormat = DepthFormat.D16;
         if ( device != null ) device.Dispose();
        
         device = new Device( 0, DeviceType.Hardware, this,
                           CreateFlags.SoftwareVertexProcessing, presentParams );
        
         if ( mymesh != null ) mymesh.Dispose();
        
         try { mymesh = Mesh.FromFile( @"C:\temp\mesh.x", MeshFlags.SystemMemory,
                                          device, out exmat );
         } catch {
            MessageBox.Show( @"Missing or Invalid: C:\temp\mesh.x" );
            return;
         } 
         
         if ( exmat != null && exmat.Length > 0 ) {
            mat = new Material[exmat.Length];
            tex = new Texture [exmat.Length];
 
            for ( int i=0; i < exmat.Length; i++ )
            {
               mat[i].Ambient = exmat[i].Material3D.Diffuse;
               if ( exmat[i].TextureFilename != null )
                  try { tex[i] = TextureLoader.FromFile( device, exmat[i].TextureFilename );
               } catch {
                  MessageBox.Show( "Missing: " + exmat[i].TextureFilename ); return; 
               } 
             }
          }
         
          device.Transform.View = Matrix.LookAtLH( new Vector3( 0f,2f,-3f ),
                                                   new Vector3( 0f,0f,0f ),
                                                   new Vector3( 0f,1f,0f ) );

          device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI/4,
                                                                  1f, 1f, 10f );
          device.RenderState.CullMode = Cull.None;
          device.RenderState.Ambient = Сolor.White;   
          device.RenderState.Lighting = true;

          myTimer.Start(); 

       } catch( DirectXException ex ) { 
          MessageBox.Show( ex.ToString() );
          return; 
       } 
     } 
   
     protected static void OnTimer( Object myObject, EventArgs myEventArgs ) {
        if (device == null) return; 
        device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.White, 1f, 0 );
        device.Transform.World = Matrix.RotationY( fAngle += 0.01f );
        device.BeginScene();
           for ( int i=0; i < mat.Length; i++ ) {
              device.Material = mat[i];
              device.SetTexture( 0, tex[i] ); mymesh.DrawSubset( i );
           } 
        device.EndScene();
        device.Present();
     }
}

Текстурированный Куб

Отрисовка куба достаточно проста : Mesh cube = Mesh.Box( device, 1f, 1f, 1f );
Однако запросы параметров Куба возвращают удивительные результаты:
int nv = cube.NumberVertices; возвращает количество вершин nv = 24 вместо ожидаемых 8-ми.
int nf = cube.NumberFaces; возвращает количество плоскостей nf = 12 вместо ожидаемых 6.
VertexFormats vf = cube.VertexFormat; возвращает Вертексный Формат vf = PositionNormal, вместо ожидаемого PositionNormalTextured.
Очевидно, что Mesh.Box кодирует каздую вершину трижды и каждый квадрат представляет в виде двух треугольников.
Mesh.Box , кроме того, не содержит текстурных координат и соответственно Mesh.Box не может быть покрыт текстурой.
Существует два решения этой проблемы:
1) Mesh.Box клонировать в виде копии с форматом вершин, дополненным текстурными координатами, "MyTexturedBox".
2) Записать х-файл cube.x, который самостоятельно определяет все Вершины, Фэйсы и Текстуры.

запись cube.x-файла:
Это задание не совсем простое, так как на каждой из 8-ми вершин висят три текстуры, но каждая вершина содержит только одну текстурную координату Tu,Tv в вертексном формате, например. PositionNormalTextured. Для покрытия 6-ти плоскостей требуется 6 текстур и каждая текстуратребует 4 текстурные координаты = всего 6*4 = 24 Текстурные координаты - отсюда становится понятно почему Mesh.Box содержит 24 вершины, а не 8.
Следствие: Из-за текстурных координат требуется программировать каждую вершину трижды, по одному разу на каждую плоскость (Фейс). Таким образом, для свободного наложения картинки на каждую плоскость куба потребуется 24 текстурированных вершины с соответствующей сложной нумерацией и большим количеством кода.

Во многих случаях такое свободное текстурирование необязательно. Если это не важно правильно ли ориентирована текстура или нет, то можно обойтись и кодированием 12-ти вершин.
Пояснение: Для начала программируется 4 Вертекса на дне от p0 до p3, а затем 4 Вертекса крышки, от p4 до p7, затем еще раз задние ребра дна p8 = p2 и p9 = p3 и оба задние ребра крышки p10 = p6 и p11 = p7.
В индекном буфере кодируется сначала передняя стенка, затем правая, затем задняя и затем левая. После - дно и крышка с помощью дублирующих Вертексов p8, p9, p10 и p11.
Текстурные координаты фронтальной стенки просты, но текстуру правой стенки нужно горизонтально отзеркалить: на последнюю линию фронтовой текстуры вешается последняя линия правой текстуры, которая заканчивается первой линией. На эту линию вешается первая линия задней текстуры, а от последней линии задней текстуры зеркалится текстура левой стороны. Затем на нижнюю строку передней текстуры навешивается нижняя линия текстуры дна, а на верхнюю строку передней текстуры верхняя линия крышки.
Через это двойное использование 6 из 12-ти граней куба - 12-ти Вертексов достаточно для полного тектурирования куба.

  

х-файл:

xof 0302txt 0064 //mandatory X-file header
Mesh { //VertexBuffer
   12; //vertex count
   0.0; 0.0; 0.0;, //p0 floor
   1.0; 0.0; 0.0;, //p1
   1.0; 0.0; 1.0;, //p2
   0.0; 0.0; 1.0;, //p3
   0.0; 1.0; 0.0;, //p4 ceiling
   1.0; 1.0; 0.0;, //p5
   1.0; 1.0; 1.0;, //p6
   0.0; 1.0; 1.0;, //p7
   1.0; 0.0; 1.0;, //p8 = p2 floor for texture 
   0.0; 0.0; 1.0;, //p9 = p3
   1.0; 1.0; 1.0;, //p10= p6 ceiling for texture   
   0.0; 1.0; 1.0;, //p11= p7
//IndexBuffer
   6; //face count
   4; 0, 1, 5, 4;, //front face
   4; 1, 2, 6, 5;, //right face
   4; 2, 3, 7, 6;, //back face
   4; 3, 0, 4, 7;, //left face
   4; 0, 1, 8, 9;, //floor face
   4; 4, 5,10,11;; //ceiling face

   MeshTextureCoords {
      12; 0.0, 1.0; //p0 floor
      1.0, 1.0; //p1
      0.0, 1.0; //p2
      1.0, 1.0; //p3
      0.0, 0.0; //p4 ceiling
      1.0, 0.0; //p5
      0.0, 0.0; //p6
      1.0, 0.0; //p7
      1.0, 0.0; //p8 floor
      0.0, 0.0; //p9
      1.0, 1.0; //p10 ceiling
      0.0, 1.0; //p11
   }

   MeshMaterialList {
      2;6;0,1,0,1,0,1;; //2 mats for 6 faces: mat 0 for faces 0,2,4, mat 1 for faces
      1,3,5
      Material {
           1.0;1.0;1.0;0;;0;0;0;0;;0;0;0;;
           TextureFilename { "C:\\temp\\bmp1.bmp"; }
      }
      Material {
          1.0;1.0;1.0;0;;0;0;0;0;;0;0;0;;
          TextureFilename { "C:\\temp\\bmp2.bmp"; }
       }
   }
} //end of Mesh file C:\temp\cube.x *************************************************************

Запишите этот Меш в C:\temp\cube.x, поменяйте в коде имя загружаемого файла и попробуйте загрузить и посмотреть cube.x.