Войти
ПрограммированиеСтатьиГрафика

Простейший пример вывода 3d модели (*.х файла) на форму.

Внимание! Этот документ ещё не опубликован.

Автор:

Здравствуйте!

Сегодня мне хочется рассказать, как нарисовать вращающийся 3d объект, используя минимум строк кода.
Исходники

Для этого нам понадобится Microsoft Visual C# Express, установленный Managed DirectX (например DirectX от сталкера) и любой файл *.x , который мы и будем выводить. 3d модель можно создать в 3d max и экспортировать с помощью бесплатного плагина Panda DirectX. Ещё нужен модуль «Вовк.cs», текст которого приведён в конце урока.

Открываем C# и создаём новый проект по стандартному шаблону Windows Form Application (Приложение Windows Form). У меня он называется Урок. В новом проекте с помощью вкладки контекстного меню «оптимизация кода» переименуем Form1 в ГлавнаяФорма. Затем создаём обработку события OnPaint стандартным способом (как в делфи): щёлкнув на молнию в режиме конструктора на панели свойств и дважды щёлкнув по строчке Paint в открывшейся таблице событий. Ещё необходимо добавить в проект модуль «Вовк.cs» с одноимённым классом.
Сейчас в классе Главная форма только две функции: конструктор и реакция на событие. Вторая пустая, а в первой вызов инициализации, сгенерированный средой. Но чтобы заполнять их надо подключить DirectX. Для этого в обозревателе решений добавим ссылку на Microsoft.DirectX, на Microsoft.DirectX.Direct3d и на всякий случай на Microsoft.DirectX.Dorect3dx. А так же две первые пропишем в начале файла директивами using.
       

      using Microsoft.DirectX;
       using Microsoft.DirectX.Direct3D;
       using d3d = Microsoft.DirectX.Direct3D;

Третья строчка для удобства обращения к классу Font, такой же есть в System.Drawing. Теперь можно объявить глобальные переменные главной формы.

        private Device Устройство = null;
        private d3d.Font текст = null;
        private Вовк.Модель модель;
        private float angle = 0;

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

        public ГлавнаяФорма()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
            ИнициализацияКомпонента();
            Устройство = Вовк.ИнициализацияГрафики(this);
            текст = new Microsoft.DirectX.Direct3D.Font(Устройство, this.Font);
            модель = Вовк.ЗагрузкаМодели("../../", "tekh.x");
            Устройство.DeviceReset += new EventHandler(ПриСбросе);
            ПриСбросе(this, new EventArgs());
        }

Стиль формы необходимо изменить, чтобы избежать мигания. Далее инициализируем устройство, создаём direct3d текст, копируя его свойство у текста заголовка формы, и загружаем модель «tekh.x» из корневой папки проекта. Следующие две строчки – ссылка на процедуру обработки для события сброса устройства и вызов этой процедуры. Её текст ниже:

        private void ПриСбросе(object sender, EventArgs e)
        {
            Вовк.SetupCamera(200);
            Вовк.SetupLights();
        }

Здесь устанавливается камера и освещение. Параметр камеры – это расстояние до начала координат, то есть до отображаемого объекта. Меняя его можно приближать объект или отдалять. Последним методом Главной Формы является рисование.

        private void ГлавнаяФорма_Paint(object sender, PaintEventArgs e)
        {
            int fps = Вовк.РасчётFPS();
            Устройство.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.CornflowerBlue, 1.0f, 0);
            angle += Вовк.Pi / 100f;

            Устройство.BeginScene();
            текст.DrawText(null, "fps = " + Convert.ToString(fps), new Point(10, 10), Color.Black);
            Устройство.Transform.World = Matrix.RotationYawPitchRoll(angle, angle, angle);
            Вовк.НарисоватьМодель(модель);
            Устройство.EndScene();

            Устройство.Present();
            this.Invalidate();
        }

Этот алгоритм самый длинный, но ни чуть не более сложный, чем предыдущие. Он разбит на три смысловые части. Сначала мы объявляем переменную fps и присваиваем ей значение соответствующей функции из модуля Вовк. Затем очищаем устройство и заполняем фон фиалковым цветом. В конце первого блока надо увеличить угол для вращения.
Вторая часть непосредственно рисует на форме картинку. Она ограничена операторными скобками BeginScene и EndScene, внутри которых всего три оператора. Первый выводит значение кадров в секунду, начиная с точки (10,10). Второй задаёт мировую матрицу как матрицу вращения по трём углам angle, А третья рисует модель.
Завершающая часть перерисовывает форму на экране компьютера, чтобы всё нарисованное в прошлом блоке отобразилось.

Текст модуля ГлавнаяФорма.cs

using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using d3d = Microsoft.DirectX.Direct3D;

namespace Урок
{
    public partial class ГлавнаяФорма : Form
    {
        private Device Устройство = null;
        private d3d.Font текст = null;
        private Вовк.Модель модель;
        private float angle = 0;

        public ГлавнаяФорма()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
            ИнициализацияКомпонента();
            Устройство = Вовк.ИнициализацияГрафики(this);
            текст = new Microsoft.DirectX.Direct3D.Font(Устройство, this.Font);
            модель = Вовк.ЗагрузкаМодели("../../", "tekh.x");
            Устройство.DeviceReset += new EventHandler(ПриСбросе);
            ПриСбросе(this, new EventArgs());
        }

        private void ПриСбросе(object sender, EventArgs e)
        {
            Вовк.SetupCamera(200);
            Вовк.SetupLights();
        }

        private void ГлавнаяФорма_Paint(object sender, PaintEventArgs e)
        {
            int fps = Вовк.РасчётFPS();
            Устройство.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.CornflowerBlue, 1.0f, 0);
            angle += Вовк.Pi / 100f;

            Устройство.BeginScene();
            текст.DrawText(null, "fps = " + Convert.ToString(fps), new Point(10, 10), Color.Black);
            Устройство.Transform.World = Matrix.RotationYawPitchRoll(angle, angle, angle);
            Вовк.НарисоватьМодель(модель);
            Устройство.EndScene();

            Устройство.Present();
            this.Invalidate();
        }

    }
}

Текст модуля Вовк.cs
using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;// mesh
//

namespace Урок
{
    class Вовк
    {
        private static Device Устройство = null;
        private static Control Форма = null; // ссылка на форму, в которой рисуем
        static int sec, fps, fps_inc;
        public const float Pi = (float)Math.PI;

        /// <summary>
        /// Структура 3d объекта
        /// </summary>
        public class Модель
        {
            public Material[] mat = null; // материалы
            public Texture[] tex = null;  // текстуры
            public Mesh mesh = null;      // тело
        }
        /// <summary>
        /// Выводит количество кадров в секунду
        /// </summary>
        /// <returns></returns>
        static public int РасчётFPS()
        {
            if (sec == DateTime.Now.Second) fps_inc++;
            else
            {
                sec = DateTime.Now.Second; fps = fps_inc;
                fps_inc = 0;
            };
            return fps;
        }
        /// <summary>
        /// Прединициализация графического устройства
        /// </summary>
        /// <param name="form">Форма рисования</param>
        /// <returns>графическое устройство</returns>
        public static Device ИнициализацияГрафики(Control form)
        {
            // Set our presentation parameters
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.AutoDepthStencilFormat = DepthFormat.D24X8;// моя GMAx3100 не поддерживает 32 битную глубину (((
            presentParams.EnableAutoDepthStencil = true;

            // Create our Устройство
            Устройство = new Device(0, DeviceType.Hardware, form, 
                CreateFlags.HardwareVertexProcessing | CreateFlags.PureDevice, presentParams);
            Форма = form;
            return Устройство;
        }

        /// <summary>
        /// Загрузка 3d модели в класс Модель
        /// </summary>
        /// <param name="Устройство">графическое устройство</param>
        /// <param name="модель">структура модели</param>
        /// <param name="path">путь к файлу 3d модели. 
        /// Пути к текстурам строятся относительно него</param>
        /// <param name="file">имя файла (с расшерением)</param>
        public static Модель ЗагрузкаМодели(string path, string file)
        {
            ExtendedMaterial[] mtrl;
            Модель mtm = new Модель();

            // Load our mesh
            if (File.Exists(path + file))
            {
                mtm.mesh = Mesh.FromFile(path + file, MeshFlags.Managed, Устройство, out mtrl);

                // If we have any materials, store them
                if ((mtrl != null) && (mtrl.Length > 0))
                {
                    mtm.mat = new Material[mtrl.Length];
                    mtm.tex = new Texture[mtrl.Length];

                    // Store each material and texture
                    for (int i = 0; i < mtrl.Length; i++)
                    {
                        mtm.mat[i] = mtrl[i].Material3D;
                        if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename != string.Empty))
                        {
                            // We have a texture, try to load it
                            mtm.tex[i] = TextureLoader.FromFile(Устройство, path + mtrl[i].TextureFilename);
                        }
                    }
                }
                else
                {   // чтобы не было ощибки при попытке их считать
                    mtm.mat = new Material[0];
                    mtm.tex = new Texture[0];
                }
            }
            else MessageBox.Show("Ошибка! Файл 3d модели не найден");
            return mtm;
        }

        /// <summary>
        /// Вывод 3d модели в форму, 
        /// указанную при инициализации устройства
        /// </summary>
        /// <param name="Устройство">графическое устройство</param>
        /// <param name="модель">структура модели</param>
        public static void НарисоватьМодель(Модель mtm)
        {
            for (int i = 0; i < mtm.mat.Length; i++)
            {
                Устройство.Material = mtm.mat[i];
                Устройство.SetTexture(0, mtm.tex[i]);
                mtm.mesh.DrawSubset(i);
            }
        }

        // SetupCamera - установка камеры
        public static Vector3 смотреть_из = new Vector3(0, 2, -10);
        public static Vector3 смотреть_на = new Vector3(0, 0, 0);
        public static Vector3 смотреть_вверх = new Vector3(0, 1, 0);
        public static float f1 = 0, f2 = 0, f3 = 0;
        /// <summary>
        /// Настройка камеры, её положения и перемещения
        /// </summary>
        public static void SetupCamera(float Расстояние)
        {
            float w = Форма.ClientRectangle.Width, h = Форма.ClientRectangle.Height;
            Устройство.Transform.Projection = Matrix.PerspectiveFovLH(Pi / 4, w / h, 0.01f, 1000f);
            смотреть_из.Z = -Расстояние;
            Устройство.Transform.View = Matrix.LookAtLH(смотреть_из, смотреть_на, смотреть_вверх);
            Устройство.RenderState.Ambient = Color.White;
        }

        public static Vector3 НаправлениеСвета = new Vector3(0, 0, 1);
        /// <summary>
        /// Расстановка источников света
        /// </summary>
        public static void SetupLights()
        {
            Устройство.RenderState.Lighting = true;    // включаем освещение
            Устройство.Lights[0].Type = LightType.Directional; // направленный
            Устройство.Lights[0].Direction = НаправлениеСвета; 
            Устройство.Lights[0].Diffuse = System.Drawing.Color.White;
            Устройство.Lights[0].Range = 1000.0f;
            Устройство.Lights[0].Enabled = true;
        }
    }
}

#C#, #DirectX, #присто

22 декабря 2009 (Обновление: 8 мар. 2012)

Комментарии [5]