Сегодня мне хочется рассказать, как нарисовать вращающийся 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 Вовк.Модель модель;
privatefloat 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» из корневой папки проекта. Следующие две строчки – ссылка на процедуру обработки для события сброса устройства и вызов этой процедуры. Её текст ниже:
Здесь устанавливается камера и освещение. Параметр камеры – это расстояние до начала координат, то есть до отображаемого объекта. Меняя его можно приближать объект или отдалять. Последним методом Главной Формы является рисование.
Этот алгоритм самый длинный, но ни чуть не более сложный, чем предыдущие. Он разбит на три смысловые части. Сначала мы объявляем переменную 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 Вовк.Модель модель;
privatefloat 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());
}
privatevoid ПриСбросе(object sender, EventArgs e)
{
Вовк.SetupCamera(200);
Вовк.SetupLights();
}
privatevoid ГлавнаяФорма_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 Вовк
{
privatestatic Device Устройство = null;
privatestatic Control Форма = null; // ссылка на форму, в которой рисуемstaticint sec, fps, fps_inc;
publicconstfloat Pi = (float)Math.PI;
/// <summary>/// Структура 3d объекта/// </summary>publicclass Модель
{
public Material[] mat = null; // материалыpublic Texture[] tex = null; // текстурыpublic Mesh mesh = null; // тело
}
/// <summary>/// Выводит количество кадров в секунду/// </summary>/// <returns></returns>staticpublicint Расчёт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>publicstatic 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>publicstatic Модель ЗагрузкаМодели(string path, string file)
{
ExtendedMaterial[] mtrl;
Модель mtm = new Модель();
// Load our meshif(File.Exists(path + file))
{
mtm.mesh = Mesh.FromFile(path + file, MeshFlags.Managed, Устройство, out mtrl);
// If we have any materials, store themif((mtrl != null) && (mtrl.Length >0))
{
mtm.mat = new Material[mtrl.Length];
mtm.tex = new Texture[mtrl.Length];
// Store each material and texturefor(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>publicstaticvoid НарисоватьМодель(Модель mtm)
{
for(int i = 0; i < mtm.mat.Length; i++)
{
Устройство.Material = mtm.mat[i];
Устройство.SetTexture(0, mtm.tex[i]);
mtm.mesh.DrawSubset(i);
}
}
// SetupCamera - установка камерыpublicstatic Vector3 смотреть_из = new Vector3(0, 2, -10);
publicstatic Vector3 смотреть_на = new Vector3(0, 0, 0);
publicstatic Vector3 смотреть_вверх = new Vector3(0, 1, 0);
publicstaticfloat f1 = 0, f2 = 0, f3 = 0;
/// <summary>/// Настройка камеры, её положения и перемещения/// </summary>publicstaticvoid 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;
}
publicstatic Vector3 НаправлениеСвета = new Vector3(0, 0, 1);
/// <summary>/// Расстановка источников света/// </summary>publicstaticvoid 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;
}
}
}