Вертексы
Представление Поверхности в виде Треугольников
В отличии от 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. Это плохая Редундантность = Балласт. | |
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 может быть длинным и не читабильным.