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

Рендеринг вращающихся кубов в DirectX

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

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

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

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

Чтобы не создавать приложение с нуля, мы воспользуемся результатами работы №10 и некоторыми предварительными преобразованиями подготовим ее для решения поставленной задачи.

  • Создайте новый пустой проект с именем RenderCube типа C#
  • Вызовите контекстное меню для имени проекта или узла References в панели Solution Explorer (или в меню Project оболочки), чтобы открыть окно добавления ссылок на необходимые для проекта библиотеки DLL
  • Добавьте к проекту, удерживая клавишу Ctrl, ссылки на библиотеки
  • System.dll
  • System.Drawing.dll
  • System.Windows.Forms.dll
  • Microsoft.DirectX.dll
  • Microsoft.DirectX.Direct3D.dll
  • В панели Solution Explorer выделите узел проекта и с помощью команды Add Existing Item меню Project оболочки добавьте к проекту файлы Form1.cs и Program.cs из лабораторной работы №10
  • Файл Form1.Designer.cs является подчиненным файлу Form1.cs и скопируется оболочкой автоматически.

    Оболочка скопирует эти файлы в каталог размещения нового проекта.

  • Убедитесь, что на данном этапе панель Solution Explorer проекта выглядит так
  • Вызовите окно Find and Replace комбинацией клавиш Ctrl-H (или командой Edit/Find and Replace/Quick Replace ), настройте его в соответствии с рисунком и щелкните на кнопке Replace All
  • Оболочка выполнит во всех файлах замену пространства имен SimpleMedia на RenderCube. Это действие нужно для того, чтобы классы приложения находились в одном (уже новом) пространстве имен и видели друг друга.

    Для того, чтобы упростить задачу - не задавать координаты нормалей, настроим наш код на работу без источника света. Для этого вместо режима использования структуры PositionNormalColored нужно применить структуру PositionColored и отключить источник света.

  • Откройте файл Form1.cs в режиме View Code, вызовите окно Find and Replace комбинацией клавиш Ctrl-H и при настройке Currend Document выполните замену PositionNormalColored на PositionColored
  • Через раскрывающийся список Members редактора кода позиционируйтесь на обработчике vb_Created() и удалите из него код создания нормалей к вершинам треугольника
  • // Дополнить вершины параметрами нормалей
                verts[0].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[1].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
                verts[2].SetNormal(new Vector3(0.0F, 0.0F, -1.0F));
  • Удалите из функции OnPaint() код настройки источника света
  • // Настроить параметры света источника с номером 0
                device.Lights[0].Type = LightType.Point;    // Точечный источник
                // По умолчанию (0,0,0) - источник в начале координат
                device.Lights[0].Position = new Vector3();  
                // Свет диффузионный белый
                device.Lights[0].Diffuse = System.Drawing.Color.White;
                // Затухание света при удалении
                device.Lights[0].Attenuation0 = 0.2f;
                // Дальность освещения, поддерживаемая устройством 
                device.Lights[0].Range = 1000.0f;
                // Принять настройки
                device.Lights[0].Commit();
                // Установить активным 
                device.Lights[0].Enabled = true;
  • В функции SetupCamera() отключите освещение сцены, установив флаг device.RenderState.Lighting конвейера в состояние false
  • Установите режим Release работы компилятора и запустите приложение, чтобы проверить, что на данном этапе оно функционирует нормально
  • Мы видим, что одновременно запускаются два окна: консольное и графическое. Отключим консольное окно.

  • В панели Solution Explorer для узла проекта вызовите контекстное меню и выполните команду Properties
  • Для вкладки Application окна свойств проекта установите свойство Output type в значение Windows Application и закройте это окно свойств
  • Запустите приложение в режиме Debug и убедитесь, что консольное окно больше не появляется
  • На этом подготовительный этап к выполнению данной лабораторной работы завершен.

    Создание вращающегося куба

    Смоделируем куб, показанный на рисунке

    У нашего куба будет 6 граней. Для представления каждой грани двумя треугольниками потребуется всего 12 треугольников, каждый из которых будет описываться 3 вершинами. Итого, для полного описания куба нужно задать координаты 36 вершин для аппроксимирующих треугольников.

  • В панели Solution Explorer вызовите контекстное меню для файла Form1.cs и выполните команду View Code
  • В верхней части рабочей области редактора раскройте правый список Members и позиционируйтесь на функции vb_Created(), которую перепишите следующим образом
  • void vb_Created(object sender, EventArgs e)
            {
                // Определить внутреннюю ссылку на вершинный буфер
                VertexBuffer buffer = (VertexBuffer)sender; // Явное приведение типов
        
                // Создать локальный массив структур непреобразованных координат
                CustomVertex.PositionColored[] verts = new
                    CustomVertex.PositionColored[36];
        
                // Задать параметры аппроксимирующих треугольников
                // Порядок обхода вершин для лицевой стороны правый
                // (по часовой стрелке), для невидимой - левый 
                //
                // Задняя грань: треугольник 2-0-1 (невидимая сторона - обход левый)
                verts[0] = new CustomVertex.PositionColored(1.0F, 1.0F, 1.0F, Color.Red.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.Red.ToArgb());
                // Задняя грань: треугольник 2-1-3 (невидимая сторона - обход левый)
                verts[3] = new CustomVertex.PositionColored(1.0F, 1.0F, 1.0F, Color.Red.ToArgb());
                verts[4] = new CustomVertex.PositionColored(-1.0F, -1.0F, 1.0F, Color.Red.ToArgb());
                verts[5] = new CustomVertex.PositionColored(1.0F, -1.0F, 1.0F, Color.Red.ToArgb());
                //
                // Передняя грань: треугольник 5-6-4 (лицевая сторона - обход правый)
                verts[6] = new CustomVertex.PositionColored(1.0F, 1.0F, -1.0F, Color.Blue.ToArgb());
                verts[7] = new CustomVertex.PositionColored(-1.0F, -1.0F, -1.0F, Color.Blue.ToArgb());
                verts[8] = new CustomVertex.PositionColored(-1.0F, 1.0F, -1.0F, Color.Blue.ToArgb());
                // Передняя грань: треугольник 5-7-6 (лицевая сторона - обход правый)
                verts[9] = new CustomVertex.PositionColored(1.0F, 1.0F, -1.0F, Color.Blue.ToArgb());
                verts[10] = new CustomVertex.PositionColored(1.0F, -1.0F, -1.0F, Color.Blue.ToArgb());
                verts[11] = new CustomVertex.PositionColored(-1.0F, -1.0F, -1.0F, Color.Blue.ToArgb());
                //
                // Верхняя грань: треугольник 0-5-4 (лицевая сторона - обход правый)
                verts[12] = new CustomVertex.PositionColored(-1.0F, 1.0F, 1.0F, Color.Yellow.ToArgb());
                verts[13] = new CustomVertex.PositionColored(1.0F, 1.0F, -1.0F, Color.Yellow.ToArgb());
                verts[14] = new CustomVertex.PositionColored(-1.0F, 1.0F, -1.0F, Color.Yellow.ToArgb());
                // Верхняя грань: треугольник 0-2-5 (лицевая сторона - обход правый)
                verts[15] = new CustomVertex.PositionColored(-1.0F, 1.0F, 1.0F, Color.Yellow.ToArgb());
                verts[16] = new CustomVertex.PositionColored(1.0F, 1.0F, 1.0F, Color.Yellow.ToArgb());
                verts[17] = new CustomVertex.PositionColored(1.0F, 1.0F, -1.0F, Color.Yellow.ToArgb());
                //
                // Нижняя грань: треугольник 1-6-7 (невидимая сторона - обход левый)
                verts[18] = new CustomVertex.PositionColored(-1.0F, -1.0F, 1.0F, Color.Black.ToArgb());
                verts[19] = new CustomVertex.PositionColored(-1.0F, -1.0F, -1.0F, Color.Black.ToArgb());
                verts[20] = new CustomVertex.PositionColored(1.0F, -1.0F, -1.0F, Color.Black.ToArgb());
                // Нижняя грань: треугольник 1-7-3 (невидимая сторона - обход левый)
                verts[21] = new CustomVertex.PositionColored(-1.0F, -1.0F, 1.0F, Color.Black.ToArgb());
                verts[22] = new CustomVertex.PositionColored(1.0F, -1.0F, -1.0F, Color.Black.ToArgb());
                verts[23] = new CustomVertex.PositionColored(1.0F, -1.0F, 1.0F, Color.Black.ToArgb());
                //
                // Левая грань: треугольник 0-6-1 (невидимая сторона - обход левый)
                verts[24] = new CustomVertex.PositionColored(-1.0F, 1.0F, 1.0F, Color.Gray.ToArgb());
                verts[25] = new CustomVertex.PositionColored(-1.0F, -1.0F, -1.0F, Color.Gray.ToArgb());
                verts[26] = new CustomVertex.PositionColored(-1.0F, -1.0F, 1.0F, Color.Gray.ToArgb());
                // Левая грань: треугольник 0-4-6 (невидимая сторона - обход левый)
                verts[27] = new CustomVertex.PositionColored(-1.0F, 1.0F, 1.0F, Color.Gray.ToArgb());
                verts[28] = new CustomVertex.PositionColored(-1.0F, 1.0F, -1.0F, Color.Gray.ToArgb());
                verts[29] = new CustomVertex.PositionColored(-1.0F, -1.0F, -1.0F, Color.Gray.ToArgb());
                //
                // Правая грань: треугольник 2-3-7 (лицевая сторона - обход правый)
                verts[30] = new CustomVertex.PositionColored(1.0F, 1.0F, 1.0F, Color.Green.ToArgb());
                verts[31] = new CustomVertex.PositionColored(1.0F, -1.0F, 1.0F, Color.Green.ToArgb());
                verts[32] = new CustomVertex.PositionColored(1.0F, -1.0F, -1.0F, Color.Green.ToArgb());
                // Правая грань: треугольник 2-7-5 (лицевая сторона - обход правый)
                verts[33] = new CustomVertex.PositionColored(1.0F, 1.0F, 1.0F, Color.Green.ToArgb());
                verts[34] = new CustomVertex.PositionColored(1.0F, -1.0F, -1.0F, Color.Green.ToArgb());
                verts[35] = new CustomVertex.PositionColored(1.0F, 1.0F, -1.0F, Color.Green.ToArgb());
        
                // Заполнить вершинный буфер данными треугольников
                buffer.SetData(verts, 0, LockFlags.None);
            }

    Координаты вершин треугольников заданы в соответствии с рисунком. Теперь нужно привести в соответствие новой задачи и остальной код программы.

  • Через раскрывающийся список Members найдите в функции InitializeGraphics() код создания вершинного буфера и измените количество вершин аппроксимирующих треугольников с 3 на 36
  • // Создать вершинный буфер
          vb = new VertexBuffer(typeof(CustomVertex.PositionColored),
                              36, 
                              device, 
                              Usage.Dynamic | Usage.WriteOnly,
                              CustomVertex.PositionColored.Format,
                              Pool.Default);

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

  • Найдите функцию SetupCamera() и закомментируйте в ней следующий код
  • // Не скрывать обратную сторону плоской фигуры
                // device.RenderState.CullMode = Cull.None;

    Изменим характер вращения куба, чтобы получить более эффективную картинку.

  • Найдите в конце функции SetupCamera() секцию кода преобразования координат объекта и замените его на следующую
  • // Преобразование пространства для произвольных поворотов
          /*
          device.Transform.World = Matrix.RotationAxis(
            new Vector3(angle / ((float)Math.PI * 2.0F),
                  angle / ((float)Math.PI * 4.0F),
                  angle / ((float)Math.PI * 6.0F)),
            angle / (float)Math.PI);
          */
              
          device.Transform.World = Matrix.RotationYawPitchRoll(
            angle / (float)Math.PI,
            angle / (float)Math.PI * 2.0F,
            angle / (float)Math.PI);
      
          angle += 0.1F;

    Здесь мы применили для поворота куба статический метод RotationYawPitchRoll() структуры Microsoft.DirectX.Matrix

    RotationYawPitchRoll(float yaw, float pitch, float roll)

    где yaw -отклонение, pitch -шаг, roll -поворот.

    Мы рисуем в данном приложении 12 примитивов-треугольников, поэтому нужно установить это количество в функции рисования DrawPrimitives() устройства.

  • Найдите в методе OnPaint() секцию кода " Сформировать сцену..." и установите в ней нужную размерность примитивов рисования
  • // Сформировать сцену с учетом параметров 
                // "положение-нормаль-цвет"
                device.BeginScene();
                // Установить формат обработки вершин при отображении
                device.VertexFormat = CustomVertex.PositionColored.Format;
                // Нарисовать треугольник по данным из буфера
                device.SetStreamSource(0, vb, 0);
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
                device.EndScene();
  • Постройте приложение и убедитесь, что рисуется вращающийся куб с цветными гранями
  • Добавление функциональности остановки вращения

    Добавим к приложению возможность останавливать и возобнавлять вращение куба, чтобы можно было полюбоваться на созданную красоту.

  • В конструкторе формы Form1() подпишитесь на событие Click самой формы, начиная вводить код this.Click+=, а дальше нажимайте клавишу Tab до создания заготовки обработчика Form1_Click()
  • Объявите и сразу инициализируйте в классе Form1 локальное булево поле flagRotate, а также заполните обработчик
  • bool flagRotate = true;
            void Form1_Click(object sender, EventArgs e)
            {
                flagRotate = !flagRotate;
            }
  • В функции SetupCamera() посадите приращение угла поворота angle на условие проверки состояния флага
  • if (flagRotate)
                    angle += 0.1F;
  • Запустите приложение и убедитесь, что щелчки на клиентской области формы управляют вращением куба
  • Создание трех вращающихся кубов

    Создадим сцену с тремя вращающимися кубами.

  • Откройте через панель Solution Explorer файл Program.cs, скопируйте из него код точки входа приложения Main() и разместите его в классе Form1 перед конструктором класса
  • public partial class Form1 : Form
        {
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
        
                using (Form1 frm = new Form1())
                {
                    // Показать форму перед созданием движка
                    frm.Show();
                    // Создать и инициализировать графический движок
                    // нашей функцией
                    frm.InitializeGraphics();
                    // Войти в цикл сообщений
                    Application.Run(frm);
                }
            }
        
            public Form1()
            {
                InitializeComponent();
                .....
            }
            .....    
        }
  • Удалите совсем файл Program.cs
  • Откройте панель Solution Explorer и щелкните правой кнопкой мыши на файле Form1.cs. Выполните команду Copy, затем щелкните правой кнопкой мыши на узле проекта RenderCube и выполните команду Paste контекстного меню.
  • Измените имя полученной копии на Form2.cs
  • Откройте файл Form2.cs в режиме View Code и замените все вхождения Form1 на Form2, вызвав окно замены комбинацией клавиш Ctrl-H и настроив его опцию Look in в значение Current Document
  • Откройте файл Form2.designer.cs и выполните в нем предыдущую замену всех вхождений Form1 на Form2
  • В меню Project оболочки выполните последнюю команду RenderCube Properties, чтобы вызвать окно свойств проекта. Установите свойство Startup object в значение начального старта второй формы
  • Запустите приложение, чтобы убедиться, что в скопированной форме Form2 предыдущий код работает
  • Теперь мы можем приступить к усложнению кода, который будет реализован в форме Form2 приложения. Долее мы хотим отобразить три вращающихся раскрашенных куба. Было бы неэффективно создавать три вершинных буфера (один буфер для каждого объекта). Можно использовать один и тот же вершинный буфер многократно, взяв за основу первый куб, а для двух других кубов перед рисованием сдвигать координаты первого куба в мировой системе координат.

  • В конце функции SetupCamera() найдите секцию кода
  • // Преобразование пространства для произвольных поворотов
          device.Transform.World = Matrix.RotationYawPitchRoll(
            angle / (float)Math.PI,
            angle / (float)Math.PI * 2.0F,
            angle / (float)Math.PI);
  • Перенесите этот код в функцию OnPaint() в место, которое находится перед функцией
  • device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
  • Добавьте после этой функции следующий код для рисования еще двух кубов, чтобы общий код секции "Сформировать сцену" выглядел так
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Вызов нашей функции установки камеры
                SetupCamera();
        
                // Сформировать сцену с учетом параметров 
                // "положение-нормаль-цвет"
                device.BeginScene();
                // Установить формат обработки вершин при отображении
                device.VertexFormat = CustomVertex.PositionColored.Format;
                // Заполнить устройство данными из буфера
                device.SetStreamSource(0, vb, 0);
        
                // Преобразование пространства для произвольных поворотов
                // Первый куб
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0F,
                    angle / (float)Math.PI);
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
                // Второй куб
                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, 0.0F, 0.0F);// Сдвиг влево  
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
                // Третий куб
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 4.0F,
                    angle / (float)Math.PI / 2.0F) *
                    Matrix.Translation(4.0F, 0.0F, 0.0F);// Сдвиг вправо
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
        
                device.EndScene();
        
                // Показать буфер кадра 
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
            }

    В коде перед рисованием очередного куба мы осуществляем преобразование вершин объекта в устройстве, применяя последовательно матрицу вращения Matrix.RotationYawPitchRoll() и матрицу перемещения Matrix.Translation(). Второй куб мы сдвигаем на 4 единицы влево относительно первого, третий - на 4 единицы вправо.

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

  • Внесите в код функции 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 = false;
        
                if (flagRotate)
                    angle += 0.02F;
            }
  • Постройте приложение, должен получиться примерно такой результат
  • Текстурирование сторон вращающегося куба

    Нанесем на стороны куба какие-нибудь рисунки. Это могут быть любые плоские bitmap -рисунки. Для текстурирования объектов в Direct3D используется понятие тексела (texel). Тексел - сокращенное название элемента текстуры или соответствующее значение цвета для каждого адреса в текстуре. Адресом в текстуре служат координаты левого верхнего угла прямоугольника текстуры (обычно 0,0) и правого нижнего угла (обычно 1, 1).

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

  • В панели Solution Explorer проекта получите через контекстное меню копию файла Form1.cs и переименуйте эту копию в файл Form3.cs
  • Вызовите комбинацией клавиш Ctrl-H окно замены текстового редактора для файла Form3.cs и замените все вхождения Form1 на Form3
  • Откройте файл Form3.designer.cs и выполните в нем те же самые замены
  • Через меню Project откройте окно свойств проекта и установите на вкладке Application в качестве стартового объект класса Form3
  • Запустите приложение, чтобы убедиться, что версия класса Form3 работоспособна и равнозначна по функциональности Form1
  • На этом подготовительная часть задачи текстурирования сторон куба завершена и можно приступать к основной части.

  • Измените тело обработчика vb_Created() так, как показано ниже
  • private void vb_Created(object sender, EventArgs e)
        {
          // Определить внутреннюю ссылку на вершинный буфер
          VertexBuffer buffer = (VertexBuffer) sender; // Явное приведение типов
      
          // Создать локальный массив структур непреобразованных координат
          CustomVertex.PositionTextured[] verts = new 
            CustomVertex.PositionTextured[36]; 
      
          // Задать параметры аппроксимирующих треугольников
          verts[0] = new CustomVertex.PositionTextured(-1.0F, 1.0F, 1.0F, 0.0F, 0.0F);
          verts[1] = new CustomVertex.PositionTextured(-1.0F, -1.0F, 1.0F, 0.0F, 1.0F);
          verts[2] = new CustomVertex.PositionTextured(1.0F, 1.0F, 1.0F, 1.0F, 0.0F);
          verts[3] = new CustomVertex.PositionTextured(-1.0F, -1.0F, 1.0F, 0.0F, 1.0F);
          verts[4] = new CustomVertex.PositionTextured(1.0F, -1.0F, 1.0F, 1.0F, 1.0F);
          verts[5] = new CustomVertex.PositionTextured(1.0F, 1.0F, 1.0F, 1.0F, 0.0F);
      
          verts[6] = new CustomVertex.PositionTextured(-1.0F, 1.0F, -1.0F, 0.0F, 0.0F);
          verts[7] = new CustomVertex.PositionTextured(1.0F, 1.0F, -1.0F, 1.0F, 0.0F);
          verts[8] = new CustomVertex.PositionTextured(-1.0F, -1.0F, -1.0F, 0.0F, 1.0F);
          verts[9] = new CustomVertex.PositionTextured(-1.0F, -1.0F, -1.0F, 0.0F, 1.0F);
          verts[10] = new CustomVertex.PositionTextured(1.0F, 1.0F, -1.0F, 1.0F, 0.0F);
          verts[11] = new CustomVertex.PositionTextured(1.0F, -1.0F, -1.0F, 1.0F, 1.0F);
      
          verts[12] = new CustomVertex.PositionTextured(-1.0F, 1.0F, 1.0F, 0.0F, 0.0F);
          verts[13] = new CustomVertex.PositionTextured(1.0F, 1.0F, -1.0F, 1.0F, 1.0F);
          verts[14] = new CustomVertex.PositionTextured(-1.0F, 1.0F, -1.0F, 0.0F, 1.0F);
          verts[15] = new CustomVertex.PositionTextured(-1.0F, 1.0F, 1.0F, 0.0F, 0.0F);
          verts[16] = new CustomVertex.PositionTextured(1.0F, 1.0F, 1.0F, 1.0F, 0.0F);
          verts[17] = new CustomVertex.PositionTextured(1.0F, 1.0F, -1.0F, 1.0F, 1.0F);
      
          verts[18] = new CustomVertex.PositionTextured(-1.0F, -1.0F, 1.0F, 0.0F, 0.0F);
          verts[19] = new CustomVertex.PositionTextured(-1.0F, -1.0F, -1.0F, 0.0F, 1.0F);
          verts[20] = new CustomVertex.PositionTextured(1.0F, -1.0F, -1.0F, 1.0F, 1.0F);
          verts[21] = new CustomVertex.PositionTextured(-1.0F, -1.0F, 1.0F, 0.0F, 0.0F);
          verts[22] = new CustomVertex.PositionTextured(1.0F, -1.0F, -1.0F, 1.0F, 1.0F);
          verts[23] = new CustomVertex.PositionTextured(1.0F, -1.0F, 1.0F, 1.0F, 0.0F);
      
          verts[24] = new CustomVertex.PositionTextured(-1.0F, 1.0F, 1.0F, 0.0F, 0.0F);
          verts[25] = new CustomVertex.PositionTextured(-1.0F, -1.0F, -1.0F, 1.0F, 1.0F);
          verts[26] = new CustomVertex.PositionTextured(-1.0F, -1.0F, 1.0F, 1.0F, 0.0F);
          verts[27] = new CustomVertex.PositionTextured(-1.0F, 1.0F, -1.0F, 0.0F, 1.0F);
          verts[28] = new CustomVertex.PositionTextured(-1.0F, -1.0F, -1.0F, 1.0F, 1.0F);
          verts[29] = new CustomVertex.PositionTextured(-1.0F, 1.0F, 1.0F, 0.0F, 0.0F);
      
          verts[30] = new CustomVertex.PositionTextured(1.0F, 1.0F, 1.0F, 0.0F, 0.0F);
          verts[31] = new CustomVertex.PositionTextured(1.0F, -1.0F, 1.0F, 1.0F, 0.0F);
          verts[32] = new CustomVertex.PositionTextured(1.0F, -1.0F, -1.0F, 1.0F, 1.0F);
          verts[33] = new CustomVertex.PositionTextured(1.0F, 1.0F, -1.0F, 0.0F, 1.0F);
          verts[34] = new CustomVertex.PositionTextured(1.0F, 1.0F, 1.0F, 0.0F, 0.0F);
          verts[35] = new CustomVertex.PositionTextured(1.0F, -1.0F, -1.0F, 1.0F, 1.0F);  
      
          // Заполнить вершинный буфер данными треугольников
          buffer.SetData(verts, 0, LockFlags.None);
        }

    При отображении каждая сторона куба будет включать текстуру целиком, то есть отображаться полностью в текстуре.

  • В функции InitializeGraphics() измените код создания вершинного буфера
  • // Создать вершинный буфер
          vb = new VertexBuffer(typeof(CustomVertex.PositionTextured),
                              36, 
                              device, 
                              Usage.Dynamic | Usage.WriteOnly,
                              CustomVertex.PositionTextured.Format,
                              Pool.Default);
  • В функции 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, 5.0F),    // Положение камеры
                    new Vector3(),              // Положение объекта текущее
                    new Vector3(0, 1, 0));      // Направление камеры
        
                // Освещение
                device.RenderState.Lighting = false;
        
                // Не скрывать обратную сторону плоской фигуры
                // device.RenderState.CullMode = Cull.None;
        
                // Преобразование пространства для произвольных поворотов
                /*
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0F,
                    angle / (float)Math.PI);
                */
                if (flagRotate)
                    angle += 0.02F;
            }
  • Перенесите вырезанный код в функцию OnPaint() и вставьте перед вызовом функции рисования DrawPrimitives(). Откорректируйте определения формата устройства
  • protected override void OnPaint(PaintEventArgs e)
            {
                // Очистить цветом клиентскую область формы
                device.Clear(ClearFlags.Target,
                    System.Drawing.Color.CornflowerBlue,
                    1.0F, 0);
        
                // Вызов нашей функции установки камеры
                SetupCamera();
        
                // Сформировать сцену с учетом параметров 
                // "положение-нормаль-цвет"
                device.BeginScene();
                // Установить формат обработки вершин при отображении
                device.VertexFormat = CustomVertex.PositionTextured.Format;
                // Нарисовать треугольник по данным из буфера
                device.SetStreamSource(0, vb, 0);
        
                // Преобразование пространства для произвольных поворотов
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0F,
                    angle / (float)Math.PI);
        
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
                device.EndScene();
        
                // Показать буфер кадра 
                device.Present();
        
                // Принудительно перерисовать
                this.Invalidate();
            }
  • Вставьте в секцию "Сформировать сцену" опять перед функцией рисования объекта код установки текстуры его сторон
  • // Сформировать сцену с учетом параметров 
                // "положение-нормаль-цвет"
                device.BeginScene();
                // Установить формат обработки вершин при отображении
                device.VertexFormat = CustomVertex.PositionTextured.Format;
                // Нарисовать треугольник по данным из буфера
                device.SetStreamSource(0, vb, 0);
        
                // Преобразование пространства для произвольных поворотов
                device.Transform.World = Matrix.RotationYawPitchRoll(
                    angle / (float)Math.PI,
                    angle / (float)Math.PI * 2.0F,
                    angle / (float)Math.PI);
        
                // Установка текстуры
                device.SetTexture(0, texture);
        
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);
                device.EndScene();

    Direct3D позволяет устанавливать до восьми типов текстур одновременно для примитива рисования. В данном коде мы устанавливаем первым параметром только одну текстуру с индексом 0. Второй параметр является ссылкой на текстуру, которую нужно еще создать. Для этого служит класс Microsoft.DirectX.Direct3D. Texture. Класс имеет четыре версии конструктора

  • public Texture(System.IntPtr lp, Microsoft.DirectX.Direct3D.Device device, Microsoft.DirectX.Direct3D.Pool pool )
  • public Texture(Microsoft.DirectX.Direct3D.Device device, int width, int height, int numLevels, Microsoft.DirectX.Direct3D.Usage usage, Microsoft.DirectX.Direct3D.Format format, Microsoft.DirectX.Direct3D.Pool pool )
  • public Texture(Microsoft.DirectX.Direct3D.Device device, System.Drawing.Bitmap image, Microsoft.DirectX.Direct3D.Usage usage, Microsoft.DirectX.Direct3D.Pool pool )
  • public Texture(Microsoft.DirectX.Direct3D.Device device, System.IO.Stream data, Microsoft.DirectX.Direct3D.Usage usage, Microsoft.DirectX.Direct3D.Pool pool )
  • Мы воспользуемся для создания текстуры предпоследней из приведенных версий. Данный конструктор имеет четыре параметра.

  • Первый параметр определяет устройство, используемое для выполнения текстуры (все ресурсы: вершинные буферы, текстуры и т.д. связаны с устройством)
  • Второй параметр определяет источник данных, используемый для текстуры. В нашем случае будет использован конструктор класса Bitmap, загружающий рисунок из внедренного ресурса
  • Третий параметр определяет индекс используемой структуры
  • Четвертый параметр - пул памяти, используемый для хранения текстуры. Для удобства будем использовать управляемый пул
  • Добавьте в секцию определения ссылок-членов класса перед функцией InitializeGraphics() код создания ссылки на текстуру
  • private Device device = null;
        private VertexBuffer vb = null;
        private Texture texture = null;
      
        public void InitializeGraphics()
        {
        ....................................
        }
  • Поместите в обработчик vb_Created() в самый его конец после заполнения вершинного буфера данными аппроксимирующих треугольников код создания управляемого пула памяти с текстурой
  • private void vb_Created(object sender, EventArgs e)
        {
          ..........................................
          // Заполнить вершинный буфер данными треугольников
          buffer.SetData(verts, 0, LockFlags.None);
      
          // Создать объект текстуры
          Bitmap bmp = new Bitmap(this.GetType(), "12_16.bmp");
          texture = new Texture(device, bmp, 0, Pool.Managed);
        }

    Здесь в качестве текстуры используется прилагаемый к описанию рисунок "12_16.bmp" из каталога Source данной лабораторной работы (вы можете создать свой рисунок в формате bmp ).

  • В меню Project выполните команду Add Existing Item, установите фильтр открывшегося диалогового окна на Image Files и добавьте в проект файл 12_16.bmp
  • Перейдите в панель Solution Explorer, выделите файл 12_16.bmp и через панель Properties установите свойство Build Action = Embedded Resource (внедренный ресурс), чтобы компилятор размещал рисунок прямо в файле исполнимой сборки
  • Постройте проект и получите примерно такой результат вращающегося текстурированного куба
  • Для демонстрации вариантов работы преподавателю создайте меню выбора варианта, как это сделано в Lab45 - WinFormsApp
  • Вернуться к учебному плану