Вертексы

Представление Поверхности в виде Треугольников

В отличии от 2D- Векторной Графики, в 3D важнейшая структура не многоугольник = Полигон, а "Face", который, как правило, является треугольником, но может быть и четырехугольником или многоугольником.
Из треугольников можно сложить полосу, которая с виду выглядит, как Полигон (массив точек). Следующий массив выглядит, как Полигон, хотя является триангулированной полосой = TriangleStrip:

Vector3[] p = { { p[ 0 ].X, p[ 0 ].Y, p[ 0 ].Z },// = p[0] 
{ p[ 1 ].X, p[ 1 ].Y, p[ 1 ].Z }, // = p[1]
{ ............................ },
{ p[ i ].X, p[ i ].Y, p[ i ].Z }, // = p[i]
{ ............................ },
{ p[n-1].X, p[n-1].Y, p[n-1].Z } // = p[n-1] };

p[0], p[1], p[2] составляют первый треугольник, на стороне  p[2]p[0] висит второй треугольник, который от вершин p[2], p[0] к следующей вершине p[3] образует продолжение полосы, а на стороне  p[2]p[3] висит третий треугольник и так далее.
Таким образом создается полоса из треугольников, которая может быть открытой или закрытой, если  последние Вершины  p[n-2] - p[1] и p[n-1] - p[0] идентичны.

Примеры одного открытого n=6 и одного закрытого n=10 TriangleStrip

TriangleStrip - это идеальная структура для поверхностей, например цилиндр, которые образуются одной полосой. Однако для поверхностей типа куб, сфера, для которых нужно несколько полос, больше подходит TriangleList и Mesh.

Базовый класс для хранения 3D-координат - это Vector3. Примеры:
Vector3 v = new Vector3( 1, 0,  0 ); // точка на оси-x на расстоянии  1.0f справа от нуля.
Vector3 v = new Vector3( 0, 0, -5 ); // точка на оси-z на расстоянии  5.0f перед дисплеем.
(Конструктор Vector3 преобразовывает автоматически int-параметр в тип float)
Базовая структура для TriangleStrip соответственно - это массив Vector3.
Пример: const int n = 100;
       Vector3[] trianglestrip = new Vector3[n];

Вертексный формат

Базовая структура 3D-координат Vector3 не содержит никакой информации о Вертексе в плане цвета, нормали и текстуры. Поэтому в DirectX сделали дополнительные одиннадцать типов данных для Вертексов.
Примеры:
CustomVertex.PositionOnly = только X,Y,Z без расширений, аналогичен Vector3.
CustomVertex.PositionColored = X,Y,Z плюс Argb-значение
CustomVertex.PositionNormal = X,Y,Z плюс Нормаль Nx,Ny,Nz
CustomVertex.PositionNormalTextured = X,Y,Z,Nx,Ny,Nz плюс коодината на текстуре Tu,Tv
CustomVertex.Transformed = X,Y,Z,W, где X,Y = 2D-ClientArea-Экранные-Координаты - например приходит в обработчик событий мыши. Z=0f,W=1f = константы= резерв.

Вертексный Буфер (Vertex Buffer)

1) Простой Вертексный массив:
Полигоны - это массивы Вертексов, которые имеют единый Вертексный формат, например CustomVertex.PositionColored.
Пример - цветной треугольник v:
CustomVertex.PositionColored[] v = new CustomVertex.PositionColored[3];
v[0].X=-1f; v[0].Y=-1f; v[0].Z=0f;
v[1].X= 1f; v[1].Y=-1f; v[1].Z=0f;
v[2].X= 0f; v[2].Y= 1f; v[2].Z=0f;
v[0].Color = System.Drawing.Color.DarkGoldenrod.ToArgb();
v[1].Color = System.Drawing.Color.MediumOrchid.ToArgb();
v[2].Color = System.Drawing.Color.Cornsilk.ToArgb();

Этот Полигон можно безо всяких дополнительных действий отобразить следующим образом:
device.BeginScene();
  device.DrawUserPrimitives( PrimitiveType.TriangleList, 1, v );
device.EndScene();

Плюс: простая программа без видимого участия VertexBuffer.
Минус: медленно работает, т.к. Полигон = Треугольник при каждой прорисовке неявно преобразуется в VertexBuffer-Формат и копируется через системную шину в графический процессор.

2) Использование Direct3D VertexBuffer:
Быстрее чем простой Вертексный массив: Полигон только один раз преобразуется в VertexBuffer-Формат и копируется в Графический процессор.
Каждая платформа, каждый язык и каждый разработчик записывает Вертексные массивы по-разному. Direct3D унифицирует это многообразие через собственный Стандарт, называемый VertexBuffer, все драйвера, поддерживающие DirectX могут на него положиться.
Direct3D-Класс VertexBuffer имеет следующую конструкцию:
VertexBuffer (
  System.Type typeVertexType,
  System.Int32 numVerts,
  Microsoft.DirectX.Direct3D.Device device,
  Microsoft.DirectX.Direct3D.Usage usage,
  Microsoft.DirectX.Direct3D.VertexFormats vertexFormat,
  Microsoft.DirectX.Direct3D.Pool pool
)

Параметр 1: typeVertexType: Тип данных равен 5. Параметр пока практически не используется.
Параметр 2: numVerts: Количество Вертексов в массиве.
Параметр 3: device: целевое устройство.
Параметр 4: usage: массив битов  = Flags (Флагов) с управляющей информацией. Отдельные биты содержат отметки, которые с помощью логического оператора "или"  = | можно сравнивать с предписанными константами. Пример: установка на 0 / Default или на WriteOnly | DoNotClip.
Параметр 5: vertexFormat: Тип данных Вертекса: например CustomVertex.PositionOnly или CustomVertex.PositionColored.
Параметр 6: pool: массив битов  = Flags (Флагов), описывающий целевое хранилище Вертексного буфера (VertexBuffer). Пример: Default = Графическая память или Managed = Основная память.

Внимание:  VertexBuffer имеет независимое подключение к своему Device, таким образом он не освобождает память, даже если все объекты, имеющие ссылки на него уничтожены. Следствие: Необходимо явно программировать уничтожение именно этого VertexBuffer через VertexBuffer.Dispose();, иначе возникнет утечка памяти (Memory leak).

3) Заполнение VertexBuffer:
VertexBuffer.SetData( v, 0, LockFlags.None ) трансформирует и копирует Вертексный массив v в  VertexBuffer. Второй параметр = начальное смещение при копировании и третий параметр = защита от конкурентного доступа к содержимому VertexBuffer-а практически неважны.
SetData заполняет VertexBuffer читабельными для DirectX-драйвера (Графической Карты) и соответствующего  Device данными (в нужном формате). Может быть бесконечно много VertexBuffer-ов . Они располагаются в основной памяти и Графическая карта ничего не знает об их существовании.

4) Передача VertexBuffer(а) на Device:
Чтобы попасть в Графическую Карту, нужно чтобы VertexBuffer был зарегистрирован в драйвере Графической Карты, как одно из транспортабельных бинарных данных = Stream.
Пример: Device.SetStreamSource( 0, vb, 0 ); означает:
1) VertexBuffer vb запакован для передачи и расположен вначале бинарных данных Stream Nr. 0 .
2) Stream Nr. 0 готов для передачи из Main Memory (через AGP- или PCI-Express-Bus) в Графическую Карту и ждет соответствующей команды.
3) Когда у Графической Карты появится время и нужный объем памяти, тогда она загрузит соответствующий Stream в Vertex-Buffer-Input-Memory и таким образом перезапишет предыдущий Stream , находящийся до этого в устройстве.
Один Stream может содержать несколько VertexBuffer-ов. Если требуется их одновременная отрисовка, то нужно воспользоваться третьим параметром функции SetStreamSource для того чтобы расположит все Stream друг за другом. Так же можно сложить Streams в резерв для последовательного вывода (Нумерация = первый параметр SetStreamSource), однако, в один момент времени, только один из них будет находится в Vertex-Buffer-Input-Memory Графической Карты.

Схема транспортной цепи Вертексов
:


Момент Передачи
:
1) Если в программе используется только один VertexBuffer или несколько упаковано в один Stream, которые нужно всегда вместе и одновременно отображать, то в этом случае - лучше всего копировать Stream один раз сразу после инициализации Device. В этом случае, векторные данные пройдут через шину (Bus) только раз и останутся в Графической Карте. И только после того, как Device станет недоступным и нужно будет инициализировать новый, тогда нужно будет снова копировать поток данных.
2) Если программа содержит два или более VertexBuffer, которые нужно выводит по-отдельности и друг за другом, то данные нужно копировать друг за другом, используя функцию OnTimer между функциями Device.BeginScene() - Device.EndScene(). Таким образом векторные данные будут постоянно проходить через шину (Bus), что более гибко, но затратно.
пример с двумя VertexBuffer vb0 и vb1, которые отрисовываются отдельно:
device.BeginScene();
  device.SetStreamSource(0, vb0, 0);
  device.VertexFormat = CustomVertex.PositionNormal.Format;
  device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
  device.SetStreamSource(0, vb1, 0);
  device.VertexFormat = CustomVertex.PositionNormalTextured.Format;
  device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
device.EndScene();


5) Отрисовка Stream:
Если Вертексные данные находятся в Графической Карте, (точнее: в Vertex-Buffer-Отделе графической памяти) то дальше все работает автоматически. Данные проходят через цепочку специализированных микропроцессоров: Tesselation->Fixed Vector Pipeline и т.д. до конца, где Backbuffer содержит уже растеризованную картинку. Только самый последний шаг нужно программировать - Device.Present(); - переключение Backbuffer во Frontbuffer.

Index Buffer

Проблема: TriangleStrips идеально подходят только тогда, когда вся фигура может быть представлена одной лентой. На сложных поверхностях Вертексы кодируются многократно (два-три и более раз), так как они состоят из двух, трех и более TriangleStrips. Это плохая Редундантность = Балласт.
Пример с восемью треугольниками:
p5 и p6 содержатся в одном треугольнике;
p0 и p8 в двух треугольниках;
p1, p3, p4 и p7 в трех треугольниках каждый;
p2 содержится в шести треугольниках.
Решени 1: закодировать поверхность двумя TriangleStrips:
левый Strip : p5,p4,p3,p2,p0,p1
правый Strip: p4,p8,p2,p7,p1,p6

Минус 1: Сложный Выбор начальной точки и последовательности точек.
Минус 2: Точки на стыке p1,p2,p4 дублируются.
Решение 2: Отделение Координатной информации от информации о Последовательности в две отдельные структуры данных:
a) Vertexbuffer для Вертексной информации, где последовательность данных произвольна
b) Indexbuffer для хранения информации о последовательности.

     

1 static Mesh mymesh; //let's use a mesh to display the sample 
2 CustomVertex.PositionColored[] myvertexbuf = { 
3 new CustomVertex.PositionColored(-1, 1, 0,Color.Red .ToArgb() ), //p0 
4 new CustomVertex.PositionColored( 0, 1, 0,Color.Green .ToArgb() ), //p1 
5 new CustomVertex.PositionColored( 0, 0, 0,Color.Blue .ToArgb() ), //p2 
6 new CustomVertex.PositionColored(-1, 0, 0,Color.Orange .ToArgb() ), //p3 
7 new CustomVertex.PositionColored( 0,-1, 0,Color.Yellow .ToArgb() ), //p4 
8 new CustomVertex.PositionColored(-1,-1, 0,Color.Brown .ToArgb() ), //p5 
9 new CustomVertex.PositionColored( 0, 1, 1,Color.Black .ToArgb() ), //p6 
10 new CustomVertex.PositionColored( 0, 0, 1,Color.Cyan .ToArgb() ), //p7 
11 new CustomVertex.PositionColored( 0,-1, 1,Color.Magenta.ToArgb() ) }; //p8 
12 Int16[] myindexbuf = { 0,1,2, 0,2,3, 3,2,4, 3,4,5, 1,6,7, 1,7,2, 2,7,8, 2,8,4 }; 
//************************************************************************** 
13 if ( mymesh != null ) mymesh.Dispose();// free the old mesh if any 
14 mymesh = new Mesh( 8, myindexbuf.Length, MeshFlags.WriteOnly, 
15 CustomVertex.PositionColored.Format, device ); 
16 mymesh.VertexBuffer.SetData( myvertexbuf, 0, LockFlags.None ); 
17 mymesh.IndexBuffer .SetData( myindexbuf , 0, LockFlags.None ); 
//************************************************************************** 
18 device.BeginScene(); 
19 mymesh.DrawSubset(0); 
20 device.EndScene(); 
21 device.Present();

Строки от 1 до 12 = глобальные определения в Form1
Строки от 13 до 17 в protected override void OnResize( ... ) после инициализации  device
Строки от 18 до 21 в protected static void OnTimer( ... )
Строка 12 Определяет IndexBuffer в форме TriangleList с вертексными именами восьми треугольников. Последовательность треугольников не имеет значения, но последовательность вершин к каждом треугольнике важна для определения нормали (лицевой/тыльной стороны).

Существует две возможности определения поверхностей через треугольники:
a) упорядоченные Вертексы описывают последовательно соединенные треугольники - TriangleStrip.
b) неупорядоченные Вертексы плюс IndexBuffer образуют связанные треугольники - TriangleList.

TriangleStrip: Кодирование полосы треугольников в форме последовательности Вертексных координат, где каждый новый Вертекс, вместе с предыдущими двумя, образует следующий треугольник.
Плюс: Низко редундантный код, т.к. каждый Вертекс повторяется не более двух раз.
Минусы:
1) Точку начала и последовательность нужно аккуратно выбирать, иначе новые треугольники могут образоваться на неверной стороне.
2) Если нужно дополнить фигуру еще одним Strip-ом, то придется некоторые Вертексы из первого Strip-а повторить во втором.
3) Нельзя быстро обратиться к Вертексу из TriangleStrip через IndexBuffer.
4) Мощная структура данных типа Mesh использует элементы только в виде TriangleLists.

TriangleList: Кодировка отдельных треугольников с помощью IndexBuffer-а.
Плюсы:
1) Треугольники можно гибко кодировать в произвольном порядке.
2) TriangleList - это базовый элемент мощной структуры - Mesh.
Минусы: Многие Вертексы встречаются в IndexBuffer-е многократно - в итоге - IndexBuffer может быть длинным и не читабильным.