Практикум прикладного программирования на C# в среде VS.NET 2005

Использование Mesh-объектов в DirectX

Показывать лекцию целиком

Файлы к лабораторной работе Вы можете скачать здесь.

Для сложных объектов нерационально задавать их координаты и индексы в программном коде. Разумнее с помощью специальных инструментов заранее подготовить данные объекта в отдельном файле, а управление этими данными организуется приложением. Внешний файл с данными графического объекта в управляемом DirectX обычно называют X-файлом по его расширению.

В управляемом DirectX есть класс Microsoft.DirectX.Direct3D.Mesh, который находится в библиотеке Microsoft.Directx.Direct3DX. Этот класс обеспечивает загрузку и управление X-файлами. Программные экземпляры этого класса с загруженными из файлов графическими данными называются Mesh-объектами.

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

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

Создание заготовки приложения

  • Выберите каталог для размещения нового проекта и создайте пустой проект C# с именем RenderMesh
  • Выполните команду меню Project/Add Reference и добавьте к проекту ссылки на следующие библиотечные сборки .NET
  • System.dll
  • System.Drawing.dll
  • System.Windows.Forms.dll
  • Microsoft.DirectX.dll
  • Microsoft.DirectX.Direct3D.dll
  • Microsoft.Directx.Direct3DX.dll
  • Примечание. В трех последних сборках выбирайте более свежие версии.

    Здесь мы добавили ссылку на новую библиотеку, расширяющую DirectX и содержащую класс Mesh.

  • Добавьте к проекту файл Form3.cs из лабораторной работы №13 - RenderBest, выполнив команду Project/Add Existing Item
  • В панели Solution Explorer переименуйте файл Form3.cs на Form1.cs
  • Вызовите комбинацией клавиш Ctrl-H окно замены, настройте его в соответствии со снимком и замените все вхождения Form3 на Form1 для всего проекта, щелкнув на кнопке Replace All. Точно также выполните замену пространств имен с RenderBest на RenderMesh, чтобы все соответствовало между собой
  • Обратите внимание, что мы не добавляем в проект using -директиву для новой библиотеки Microsoft.Directx.Direct3DX.dll, потому что она расширяющая и в ней используется пространство имен Microsoft.DirectX.Direct3D, которое уже подключено в нашем проекте.

  • Отключите в приложении консольное окно через команду меню Project/RenderMesh Properties, установив во вкладке Application опцию Output type в значение Windows Application
  • Постройте проект, чтобы убедиться в работоспособности данной заготовки. Результат должен соответствовать варианту Form3 лабораторной работы №13
  • Теперь мы уберем из полученной заготовки все лишнее, что не понадобится при использовании Mesh -объектов. Учитывая, что Mesh -объект имеет свои встроенные вершинный и индексный буферы и сам определяет свойства формата вершин, удалим соответствующий код из заготовки приложения. Далее мы будем приводить секции кода, выделенные красным цветом, именно их необходимо удалять из заготовки приложения. Для быстрого позиционирования пользуйтесь раскрывающимся списком Members редактора оболочки.

  • Найдите в коде класса Form1 описание ссылочных переменных вершинного и индексного буфера и удалите их
  • private Device device = null;
        private VertexBuffer vb = null;
        private IndexBuffer ib = null;
  • Отредактируйте функцию InitializeGraphics(), удалив из нее код, связанный с вершинным и индексным буфером
  • public void InitializeGraphics()
            {
                // Создание объекта и настройка параметров представления
                // Создать объект параметров представления
                PresentParameters presentParams = new PresentParameters();
                // Установить оконный режим
                presentParams.Windowed = true;
                // Сбрасывать содержимое буфера, если он не готов к представлению
                presentParams.SwapEffect = SwapEffect.Discard;
        
                // Настройка буфера глубины для параметров устройства
                presentParams.EnableAutoDepthStencil = true;
                presentParams.AutoDepthStencilFormat =
                    Microsoft.DirectX.Direct3D.DepthFormat.D16;
        
                // Создать объект устройства и сохранить ссылку на него
                device = new Device(0, DeviceType.Hardware, this,
                    CreateFlags.SoftwareVertexProcessing, presentParams);
        
                // Создать вершинный буфер
                vb = new VertexBuffer(typeof(CustomVertex.PositionColored),
                                      8,
                                      device,
                                      Usage.Dynamic | Usage.WriteOnly,
                                      CustomVertex.PositionColored.Format,
                                      Pool.Default);
        
                // Регистрация события Created вершинного буфера
                vb.Created += new EventHandler(vb_Created);
        
                // Принудительный вызов создания треугольника 
                // и заполнения вершинного буфера при первом 
                // создании формы
                vb_Created(vb, null);
        
                // Создать индексный буфер для координат вершин куба
                ib = new IndexBuffer(typeof(short),
                                     indices.Length,
                                     device,
                                     Usage.WriteOnly,
                                     Pool.Default);
        
                // Регистрация события Created индексного буфера
                // Код написать вручную (для создания обработчика)!!!
                ib.Created += new EventHandler(ib_Created);
        
                // Принудительный вызов обработчика при первом запуске
                ib_Created(ib, null);
            }
  • Удалите целиком функцию vb_Created() -обработчик события повторного заполнения вершинного буфера
  • void vb_Created(object sender, EventArgs e)
            {
                // Определить внутреннюю ссылку на вершинный буфер
                VertexBuffer buffer = (VertexBuffer)sender; // Явное приведение типов
        
                // Создать локальный массив структур непреобразованных координат
                CustomVertex.PositionColored[] verts = 
                    new CustomVertex.PositionColored[8];
        
                // Задать параметры вершин куба в соответствии с рисунком
                //
                verts[0] = new CustomVertex.PositionColored(-1.0F, 1.0F, 1.0F, Color.Purple.ToArgb());
                verts[1] = new CustomVertex.PositionColored(-1.0F, -1.0F, 1.0F, Color.Red.ToArgb());
                verts[2] = new CustomVertex.PositionColored(1.0F, 1.0F, 1.0F, Color.Blue.ToArgb());
                verts[3] = new CustomVertex.PositionColored(1.0F, -1.0F, 1.0F, Color.Yellow.ToArgb());
                verts[4] = new CustomVertex.PositionColored(-1.0F, 1.0F, -1.0F, Color.Gold.ToArgb());
                verts[5] = new CustomVertex.PositionColored(1.0F, 1.0F, -1.0F, Color.Green.ToArgb());
                verts[6] = new CustomVertex.PositionColored(-1.0F, -1.0F, -1.0F, Color.Black.ToArgb());
                verts[7] = new CustomVertex.PositionColored(1.0F, -1.0F, -1.0F, Color.WhiteSmoke.ToArgb());
        
                // Заполнить вершинный буфер данными треугольников
                buffer.SetData(verts, 0, LockFlags.None);
            }
  • Удалите код создание массива индексов вершин треугольных примитивов
  • // Определение индексного массива вершин треугольных примитивов
            private static readonly short[] indices = {
                2,0,1,// Задняя грань:    треугольник 2-0-1 (невидимая сторона - обход левый)
                2,1,3,// Задняя грань:    треугольник 2-1-3 (невидимая сторона - обход левый)
                5,6,4,// Передняя грань:  треугольник 5-6-4 (лицевая сторона - обход правый)
                5,7,6,// Передняя грань:  треугольник 5-7-6 (лицевая сторона - обход правый)
                0,5,4,// Верхняя грань:   треугольник 0-5-4 (лицевая сторона - обход правый)
                0,2,5,// Верхняя грань:   треугольник 0-2-5 (лицевая сторона - обход правый)
                1,6,7,// Нижняя грань:    треугольник 1-6-7 (невидимая сторона - обход левый)
                1,7,3,// Нижняя грань:    треугольник 1-7-3 (невидимая сторона - обход левый)
                0,6,1,// Левая грань:     треугольник 0-6-1 (невидимая сторона - обход левый)
                0,4,6,// Левая грань:     треугольник 0-4-6 (невидимая сторона - обход левый)
                2,3,7,// Правая грань:    треугольник 2-3-7 (лицевая сторона - обход правый)
                2,7,5 // Правая грань:    треугольник 2-7-5 (лицевая сторона - обход правый)
        };
  • Удалите функцию ib_Created() -обработчик события повторного заполнения индексного буфера
  • // Обработчик события повторного заполнения индексного буфера
            void ib_Created(object sender, EventArgs e)
            {
                IndexBuffer buffer = (IndexBuffer)sender;
                buffer.SetData(indices, 0, LockFlags.None);
            }

    Объект Mesh сам следит за восстановлением вершинного, индексного буферов и формата вершин всякий раз, когда объект подвергается рендерингу.

  • Удалите из функции DrawCubes() код рисования трех дополнительных кубов
  • private void DrawCubes()
            {
                //
                // Средний ряд
                //
                //........................................................
                // Дополнительные кубы
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0f,
                    angle / (float)Math.PI / 4.0f) *
                    Matrix.Translation(
                        0.0f,
                        (float)Math.Cos(angle),
                        (float)Math.Sin(angle));
                device.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList, 0, 0, 8, 0, indices.Length / 3);
        
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI / 2.0f,
                    angle / (float)Math.PI * 4.0f) *
                    Matrix.Translation(
                        4.0f,
                        (float)Math.Sin(angle),
                        (float)Math.Cos(angle));
                device.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList, 0, 0, 8, 0, indices.Length / 3);
        
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0f,
                    angle / (float)Math.PI / 2.0f) *
                    Matrix.Translation(
                    -4.0f,
                    (float)Math.Cos(angle),
                    (float)Math.Sin(angle));
                device.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList, 0, 0, 8, 0, indices.Length / 3);
        
                if (flagRotate)
                    angle += 0.02F;
            }
  • Удалите в функции OnPaint() секцию кода, подключающую к устройству вершинный и индексный буферы
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target 
                    | ClearFlags.ZBuffer,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Вызов нашей функции установки камеры
                SetupCamera();
        
                // Сформировать сцену с учетом параметров 
                // "положение-нормаль-цвет"
                device.BeginScene();
                // Установить формат обработки вершин при отображении
                device.VertexFormat = CustomVertex.PositionColored.Format;
                // Нарисовать треугольник по данным из буфера
                device.SetStreamSource(0, vb, 0);
                device.Indices = ib;
                DrawCubes();
                device.EndScene();
        
                // Показать буфер кадра 
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
            }

    Теперь пришла пора добавить код, реализующий Mesh -технологию.

    Создание Mesh-объекта

  • Добавьте в класс создание ссылки на Mesh -объект. Ее можно разместить сразу после создания ссылки устройства
  • private Device device = null;
        private Mesh mesh=null;
      
        public void InitializeGraphics()
        {
        ...........................................
        }

    Имеются три метода создания самого объекта Mesh, но на данном этапе нам не понадобится ни один из них. Мы воспользуемся статическим методом создания куба Mesh.Box. Кроме этого метода класс Mesh содержит еще ряд перегружаемых методов для создания примитивных объектов:

  • Microsoft.DirectX.Direct3D.Mesh.Box() - создание куба
  • Microsoft.DirectX.Direct3D.Mesh.Cylinder() - создание цилиндра
  • Microsoft.DirectX.Direct3D.Mesh.Polygon() - создание полигона
  • Microsoft.DirectX.Direct3D.Mesh.Sphere() - создание сферы
  • Microsoft.DirectX.Direct3D.Mesh.Torus() - создание тора
  • Microsoft.DirectX.Direct3D.Mesh.Teapot() - создание изображения заварочного чайника
  • Этими методами мы воспользуемся в лабораторной работе №17 для создания препятствий на дороге.

  • Добавьте в конец функции InitializeGraphics() после кода создания устройства следующую строку генерации куба
  • public void InitializeGraphics()
            {
                // Создание объекта и настройка параметров представления
                // Создать объект параметров представления
                PresentParameters presentParams = new PresentParameters();
                // Установить оконный режим
                presentParams.Windowed = true;
                // Сбрасывать содержимое буфера, если он не готов к представлению
                presentParams.SwapEffect = SwapEffect.Discard;
        
                // Создать объект устройства и сохранить ссылку на него
                device = new Device(0, DeviceType.Hardware, this,
                    CreateFlags.SoftwareVertexProcessing, presentParams);
        
                // Генерация и подключение к устройству куба 
                // с помощью статического метода класса Mesh
                mesh = Mesh.Box(device, 2.0F, 2.0F, 2.0F);
            }

    Статический метод Mesh.Box() имеет две перегрузки и мы воспользовались первой из них:

  • public static Microsoft.DirectX.Direct3D.Mesh Box(Microsoft.DirectX.Direct3D.Device device, float width, float height, float depth)
  • public static Microsoft.DirectX.Direct3D.Mesh Box(Microsoft.DirectX.Direct3D.Device device, float width, float height, float depth, out Microsoft.DirectX.Direct3D.GraphicsStream adjacency)
  • Он создает новый Mesh-объект, который содержит вершины и индексы, необходимые для рендеринга куба с заданной шириной, высотой и глубиной (значение 2.0F ). Этот куб того же размера, что и созданный нами вручную в Form3 лабораторной работы №13.

    Теперь нужно отобразить Mesh -объект на экране с помощью функции DrawSubset() экземплярного объекта mesh.

  • Измените в функции DrawCubes() все вызовы метода DrawIndexedPrimitives() устройства на вызовы метода DrawSubset(0) объекта mesh
  • private void DrawCubes()
            {
                //
                // Средний ряд
                //
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0f,
                    angle / (float)Math.PI / 4.0f) *
                    Matrix.Translation(0.0f, 0.0f, 0.0f);   // По центру
                mesh.DrawSubset(0);
        
                //
                // .......................................................
                //
        
                if (flagRotate)
                    angle += 0.02F;
            }

    Обратите внимание на то, сколько много кода мы удалили, а добавили совсем чуть-чуть. Прямая выгода в использовании Mesh -технологии налицо!

  • Запустите приложение - должен получиться такой результат
  • Итак, мы получили девять вращающихся кубов, но все они оказались бесцветными. Это происходит потому, что цвета Mesh -объекта не были определены и, поскольку у нас выключена подсветка сцены, кубы остались неокрашенными и неосвещенными. Давайте будем это исправлять. Освещение в устройстве device установлено по умолчанию, поэтому можно удалить код выключения освещения или задать соответствующему параметру значение true.

  • Найдите в классе функцию SetupCamera() и замените в секции выключения освещения значение false на true
  • // Установка камеры в сцену
            private void SetupCamera()
            {
                //Создание перспективы 
                device.Transform.Projection = Matrix.PerspectiveFovLH(
                    (float)Math.PI / 4, // Угол зрения равен 45 градусов
                                        // Форматное соотношение сторон
                    (float)this.ClientSize.Width / (float)this.ClientSize.Height, 
                    1.0F,               // Ближний план
                    100.0F);            // Дальний план
        
                //Добавление камеры 
                device.Transform.View = Matrix.LookAtLH(
                    new Vector3(0, 0, 15.0F),    // Положение камеры
                    new Vector3(),              // Положение объекта текущее
                    new Vector3(0, 1, 0));      // Направление камеры
        
                // Освещение
                device.RenderState.Lighting = true;
            }
  • Запустите приложение, получится такой результат
  • Теперь наши вращающиеся кубы превратились в черные. В конце лабораторной работы №10 говорилось о том, что Direct3D не может освещать объект, не имея нормали к каждой его поверхности. Нормаль представляет собой вектор, перпендикулярный передней стороне объекта.

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

  • Добавьте в конец функции SetupCamera() строку, описывающую состояние рендера подсветки
  • // Установка камеры в сцену
            private void SetupCamera()
            {
                //Создание перспективы 
                device.Transform.Projection = Matrix.PerspectiveFovLH(
                    (float)Math.PI / 4, // Угол зрения равен 45 градусов
                                        // Форматное соотношение сторон
                    (float)this.ClientSize.Width / (float)this.ClientSize.Height, 
                    1.0F,               // Ближний план
                    100.0F);            // Дальний план
        
                //Добавление камеры 
                device.Transform.View = Matrix.LookAtLH(
                    new Vector3(0, 0, 15.0F),    // Положение камеры
                    new Vector3(),              // Положение объекта текущее
                    new Vector3(0, 1, 0));      // Направление камеры
        
                // Освещение
                device.RenderState.Lighting = true;
        
                // Установить красный свет
                device.RenderState.Ambient = Color.Red;
            }

    Общий свет и расцветка в сцене определяются состоянием рендера общего освещения. Здесь мы хотим, чтобы наш общий свет стал красным. Если сейчас запустить приложение, то ничего не изменится - кубы будут черными.

    Добавление материалов и освещения

    Для того, чтобы Direct3D правильно вычислял цвет в отдельных точках на трехмерном объекте, кроме цвета источника света нужно еще указать, как объект будет отражать свет определенного цвета. Нам необходимо описать, как поверхность кубов будет отражать свет от источника. Эту особенность задают с помощью материалов. Материалы определяют, каким образом объект будет отражать общий диффузионный свет, какие участки будут освещены больше или меньше, и отражает ли объект свет вообще.

  • Добавьте в начало функции DrawCubes() секцию кода создания материала
  • private void DrawCubes()
            {
                // Создаем материал поверхности отражения
                // и добавляем его в устройство 
                Material boxMaterial = new Material();
                boxMaterial.Ambient = Color.White;
                boxMaterial.Diffuse = Color.White;
                device.Material = boxMaterial;
        
                //
                // .................................................
                //
        
                if (flagRotate)
                    angle += 0.02F;
            }

    Здесь мы создаем новый материал и настраиваем в нем значения общего и диффузионного освещения, выбрав белый цвет. Использование белого означает, что мы отразим весь общий и диффузный свет, падающий на объекты. Затем мы передаем этот материал в устройство для рендеринга.

  • Запустите приложение и появятся красные кубы
  • Если изменить цвет источника общего света, то изменится и цвет кубов. А если изменить цвет материала на отличный от белого, то отражаемый свет будет смесью цвета материала и цвета источника.

    Использование более реалистичного освещения

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

  • Добавьте в конец функции SetupCamera() следующую секцию кода
  • // Установка камеры в сцену
            private void SetupCamera()
            {
                //Создание перспективы 
                device.Transform.Projection = Matrix.PerspectiveFovLH(
                    (float)Math.PI / 4, // Угол зрения равен 45 градусов
                                        // Форматное соотношение сторон
                    (float)this.ClientSize.Width / (float)this.ClientSize.Height, 
                    1.0F,               // Ближний план
                    100.0F);            // Дальний план
        
                //Добавление камеры 
                device.Transform.View = Matrix.LookAtLH(
                    new Vector3(0, 0, 15.0F),    // Положение камеры
                    new Vector3(),              // Положение объекта текущее
                    new Vector3(0, 1, 0));      // Направление камеры
        
                // Освещение
                device.RenderState.Lighting = true;
        
                // Установить красный свет
                device.RenderState.Ambient = Color.Red;
        
                // Настроить общий свет
                device.Lights[0].Type = LightType.Directional;
                device.Lights[0].Diffuse = Color.White;
                device.Lights[0].Direction = new Vector3(0, -1, -1);
                device.Lights[0].Commit();
                device.Lights[0].Enabled = true;
            }

    Мы создали источник направленного белого света и задали направление освещения.

  • Запустите приложение и убедитесь, что направленые к наблюдателю грани кубов освещены полностью, а повернутые грани кажутся более затененными
  • Попробуйте создать с теми же настройками света изображение заварочного чайника. Для этого:

  • Замените в функции InitializeGraphics() создание куба на код создания заварочного чайника
  • public void InitializeGraphics()
            {
                // .................................................
                //mesh = Mesh.Box(device, 2.0F, 2.0F, 2.0F);
                mesh = Mesh.Teapot(device);
            }
  • Постройте приложение и получите следующую картинку
  • Некоторые части объекта проступают на передний план из-за того, что ранее мы удалили буфер глубины.

  • Добавьте в функцию InitializeGraphics() буфер глубины, а в функцию OnPaint() флаг существования буфера глубины, как мы это делали в Form3 лабораторной работы №13 или будем делать в конце этой лабораторной работы №14
  • Запустите приложение с заварочными чайниками и убедитесь, что буфер глубины работает
  • Уберите код буфера глубины и код заварочного чайника и вновь включите код генерации кубов
  • Загрузка Mesh-объектов из внешних файлов

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

    Для быстрой и реалистичной обработки трехмерных графических объектов существуют файлы с программным кодом, который напрямую работает с графическим устройством. В этих файлах находится код, который написан на специальном языке, называемом HLSL ( High Level Shader Language - высокоуровневый язык программирования для построения теней). Программы, написанные на этом языке, называются шейдерами, а файлы, в которых хранятся шейдеры - файлами шейдеров.

    Кроме статических методов создания графических примитивов (куб, тор, ...) в классе Mesh содержатся еще два статических метода Mesh.FromFile() и Mesh.FromStream(). Эти методы имеют большое количество перегрузок, но корневые перегрузки выглядят так

  • public static Microsoft.DirectX.Direct3D.Mesh FromFile ( 
             System.String filename ,                       // Источник  данных 
             Microsoft.DirectX.Direct3D.MeshFlags options , // Как нужно загружать данные 
             Microsoft.DirectX.Direct3D.Device device ,     // Устройство 
             Microsoft.DirectX.Direct3D.GraphicsStream adjacency , // Смежные вершины 
             out Microsoft.DirectX.Direct3D.ExtendedMaterial[] materials , // Сохраненные материалы 
             Microsoft.DirectX.Direct3D.EffectInstance effects     // Файл шейдера для поддержки Mesh-объекта 
          )
  • public static Microsoft.DirectX.Direct3D.Mesh FromStream ( 
             System.IO.Stream stream ,                             // Источник данных 
             System.Int32 readBytes ,                              // Сколько байт загружать из потока 
             Microsoft.DirectX.Direct3D.MeshFlags options ,        // Как нужно загружать данные 
             Microsoft.DirectX.Direct3D.Device device ,            // Устройство 
             Microsoft.DirectX.Direct3D.GraphicsStream adjacency , // Смежные вершины 
             out Microsoft.DirectX.Direct3D.ExtendedMaterial[] materials , // Сохраненные материалы 
             Microsoft.DirectX.Direct3D.EffectInstance effects     // Файл шейдера для поддержки Mesh-объекта 
          )
  • Параметр options может быть представлен поразрядной комбинацией значений. Из множества перегрузок нужно выбирать такой метод загрузки данных, для которого имеется вся необходимая информация.

    Для демонстрации загрузки Mesh -данных из файла создадим форму Form2

  • Закройте все документы текущего редактирования командой оболочки Window/Close All Documents
  • Через панель Solution Explorer скопируйте файл Form1.cs и переименуйте его в файл Form2.cs
  • Откройте файлы Form2.cs (в режиме View Code ) и Form2.designer.cs и замените в них все вхождения Form1 на Form2 с помощью окна замены, вызываемого комбинацией клавиш Ctrl-H (окно замены должно быть настроено в соответствии со снимком: на поиск вхождений в открытых документах)
  • Установите через меню Project/RenderMesh Properties форму Form2 стартовой, постройте приложение и убедитесь, что форма Form2 сохранила функциональность Form1
  • Теперь пришла пора добавить в форму Form2 код, который будет загружать сложный Mesh -объект из X-файла.

  • Добавьте в класс Form2 объявления полей-ссылок объектов материалов и текстур, которые будут адресовать соответствующие данные, загруженные из X-файла
  • private Device device = null;
            private Mesh mesh = null;
        
            // Ссылки на массивы для загрузки из X-файла
            private Material[] meshMaterials;
            private Texture[] meshTextures;
        
            public void InitializeGraphics()
            {
                // .....................................................
            }
  • Создайте функцию-член класса Form2 для загрузки Mesh -объекта из файла (аргументом функции будет имя файла). Назовите функцию LoadMesh(), она будет возвращать ссылку на загруженный Mesh -объект.
  • // Функция загрузки Mesh-объекта из файла
            private Mesh LoadMesh(string file)
            {
                ExtendedMaterial[] data; // Создаем ссылку на загружаемые данные
                // Загружаем объект из файла
                Mesh localMesh = Mesh.FromFile(file, MeshFlags.Managed, device, out data);
        
                // Если есть данные - разделяем и сохраняем их
                if ((data != null)  (data.Length > 0))
                {
                    // Создаем массив материалов
                    meshMaterials = new Material[data.Length];
                    // Создаем массив текстур
                    meshTextures = new Texture[data.Length];
        
                    // Заполняем массив материалами и текстурами
                    for (int i = 0; i < data.Length; i++)
                    {
                        meshMaterials[i] = data[i].Material3D;
                        if ((data[i].TextureFilename != null)
                             (data[i].TextureFilename != string.Empty))
                        {
                            // Структура существует, попробуем ее загрузить
                            meshTextures[i] = TextureLoader.FromFile(
                                device, data[i].TextureFilename);
                        }
                    }
                }
        
                return localMesh;
            }
  • Удалите из класса функцию DrawCubes() на ее место вставьте новую функцию рисования объекта DrawMesh()
  • private void DrawMesh()
            {
                // Вращаем объект
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0f,
                    angle / (float)Math.PI / 4.0f) *
                    Matrix.Translation(0.0f, 0.0f, 0.0f);
        
                // Загружаем в устройство материал и текстуру поверхности отражения
                for (int i = 0; i < meshMaterials.Length; i++)
                {
                    device.Material = meshMaterials[i];
                    device.SetTexture(0, meshTextures[i]);
                    mesh.DrawSubset(0);
                }
        
                if (flagRotate)
                    angle += 0.02F;
            }
  • Измените в функции OnPaint() вызов функции рисования
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Вызов нашей функции установки камеры
                SetupCamera();
        
                // Сформировать сцену
                device.BeginScene();
                DrawMesh();
                device.EndScene();
        
                // Показать буфер кадра 
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
            }
  • Чтобы отображаемый объект не казался слишком большим, замените в функции SetupCamera() настройки масштаба изображения камеры следующими значениями
  • // Установка камеры в сцену
            private void SetupCamera()
            {
                //Создание перспективы 
                device.Transform.Projection = Matrix.PerspectiveFovLH(
                    (float)Math.PI / 4, // Угол зрения равен 45 градусов
                                        // Форматное соотношение сторон
                    (float)this.ClientSize.Width / (float)this.ClientSize.Height, 
                    1.0F,               // Ближний план
                    1000.0F);           // Дальний план
        
                //Добавление камеры 
                device.Transform.View = Matrix.LookAtLH(
                    new Vector3(0, 0, 700.0F),  // Положение камеры
                    new Vector3(),              // Положение объекта текущее
                    new Vector3(0, 1, 0));      // Направление камеры
        
                // Освещение
                device.RenderState.Lighting = true;
        
                // Установить красный свет
                device.RenderState.Ambient = Color.Red;
        
                // Настроить общий свет
                device.Lights[0].Type = LightType.Directional;
                device.Lights[0].Diffuse = Color.White;
                device.Lights[0].Direction = new Vector3(0, -1, -1);
                device.Lights[0].Commit();
                device.Lights[0].Enabled = true;
            }
  • Удалите из функции InitializeGraphics() код генерации куба и вместо него поставьте вызов функции LoadMesh() загрузки Mesh -объекта из файла
  • public void InitializeGraphics()
            {
                // Создание объекта и настройка параметров представления
                // Создать объект параметров представления
                PresentParameters presentParams = new PresentParameters();
                // Установить оконный режим
                presentParams.Windowed = true;
                // Сбрасывать содержимое буфера, если он не готов к представлению
                presentParams.SwapEffect = SwapEffect.Discard;
        
                // Создать объект устройства и сохранить ссылку на него
                device = new Device(0, DeviceType.Hardware, this,
                    CreateFlags.SoftwareVertexProcessing, presentParams);
        
                // Генерация и подключение к устройству куба 
                // с помощью статического метода класса Mesh
                mesh = Mesh.Box(device, 2.0F, 2.0F, 2.0F);
        
                // Загрузить Mesh-объект из файла tiny.x
                mesh = LoadMesh("tiny.x");
            }
  • В панели Solution Explorer вызовите контекстное меню для узла проекта и командой Add/Existing Item добавьте к проекту из прилагаемого к работе каталога Source файлы tiny.x и Tiny_skin.bmp, предварительно настроив фильтр окна подкачки
  • В панели Solution Explorer выделите одновременно скопированные оболочкой в проект файлы и щелчкните по пиктограмме Properties, чтобы вызвать для них панель настройки свойств оболочки
  • Настройте панель свойств в соответствии со снимком, чтобы оболочка при компиляции копировала в каталог bin размещения сборки самые свежие версии указанных файлов
  • Запустите приложение и обратите внимание, что некоторые части объекта не закрываются передним планом
  • Это происходит потому, что мы ранее удалили буфер глубины из параметров устройства. Исправим это:

  • Добавьте буфер глубины в функцию InitializeGraphics() перед созданием устройства
  • public void InitializeGraphics()
            {
                // Создание объекта и настройка параметров представления
                // Создать объект параметров представления
                PresentParameters presentParams = new PresentParameters();
                // Установить оконный режим
                presentParams.Windowed = true;
                // Сбрасывать содержимое буфера, если он не готов к представлению
                presentParams.SwapEffect = SwapEffect.Discard;
        
                // Настройка буфера глубины для параметров устройства
                presentParams.EnableAutoDepthStencil = true;
                presentParams.AutoDepthStencilFormat =
                    Microsoft.DirectX.Direct3D.DepthFormat.D16;
        
                // Создать объект устройства и сохранить ссылку на него
                device = new Device(0, DeviceType.Hardware, this,
                    CreateFlags.SoftwareVertexProcessing, presentParams);
        
                // Генерация и подключение к устройству куба 
                // с помощью статического метода класса Mesh
                mesh = Mesh.Box(device, 2.0F, 2.0F, 2.0F);
        
                // Загрузить Mesh-объект из файла tiny.x
                mesh = LoadMesh("tiny.x");
            }
  • Добавьте в функцию OnPaint() флаг очистки устройства с учетом существования буфера глубины
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target 
                    | ClearFlags.ZBuffer,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Вызов нашей функции установки камеры
                SetupCamera();
        
                // Сформировать сцену
                device.BeginScene();
                DrawMesh();
                device.EndScene();
        
                // Показать буфер кадра 
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
            }
  • Запустите приложение и полюбуйтесь на сложный вращающийся Mesh -объект, который мы загрузили из внешнего X-файла
  • Сдайте работу преподавателю
  • Вернуться к учебному плану