Возможно вы искали: 'Zapitalism!'

May 11 2025 07:12:28
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96001592
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18342
• Обзор The Walking ... 18785
• Обзор DMC: Devil M... 19861
• Обзор на игру Valk... 15866
• Обзор на игру Stars! 17752
• Обзор на Far Cry 3 17933
• Обзор на Resident ... 16011
• Обзор на Chivalry:... 17494
• Обзор на игру Kerb... 17969
• Обзор игры 007: Fr... 16600
Превью о играх
• Превью к игре Comp... 17944
• Превью о игре Mage... 14447
• Превью Incredible ... 14704
• Превью Firefall 13460
• Превью Dead Space 3 16324
• Превью о игре SimC... 14713
• Превью к игре Fuse 15430
• Превью Red Orche... 15531
• Превью Gothic 3 16331
• Превью Black & W... 17342
Главная » Статьи » Всё о XNA » Blender + Xna + Xen = ??? (часть 2)

Blender + Xna + Xen = ??? (часть 2)

Реализация загрузки сцены.

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

Я постарался писать код как можно проще и там где надо будут (я надеюсь) исчерпывающие комментарии. Но начнем мы со структуры проекта.



Прежде всего, обратите внимание на ссылки. В них присутствуют ссылки на библиотеки Microsoft.Xna.Framework и на библиотеки Xen.

Проект контента в данном проекте содержит два Xml файла, level.xml и level.materials.xml. Оба этих файла мы получили при выполнении экспорта сцены из Blender. Оба файла не компилируются, а просто копируются в выходную папку в исходном виде. Читать оба файла бы будем с использованием класса XmlDocument. В папке Textures расположен набор текстур, которые могут быть использованы в качестве материалов геометрии сцены. Я не стал отображать все его содержимое для экономии места.

Обратите внимание на папку Effects, которая находится вне проекта контента и содержит файл SkyBox.fx – эффект, используемый для наложения кубической карты небесного куба на куб. Эта часть проекта (в том числе описание XenSkyBox.cs будет описана в конце статьи в качестве бонуса).
Теперь по порядку хотел бы описать содержание файлов исходных кодов проекта (описывать файлы буду не в том порядке, в каком они расположены в проекте):
  • Game1.cs – основной класс игры. С него выполняется запуск игры.
  • BezTriple.cs – класс, представляющий собой узел кривой анимации. Содержит информацию о положении на временной шкале и вектора манипуляторов, используемые для интерполяции с использованием кривых Безье (класс с аналогичным названием присутствует и в документации к Blender Python API).
  • IpoCurve.cs – реализация кривой анимации на основе кубических кривых Безье.
  • IpoType.cs – перечисление, используемое для получения кривой анимации из коллекции по имени (работает быстрее чем по строковому имени).*
  • Ipo.cs – коллекция кривых анимации для одного объекта сцены.
  • TextureLoader.cs – реализация загрузчика текстур из всех форматов поддерживаемых XNA (в том числе и xnb).
  • XenReadHelper.cs – методы, облегчающие загрузку данных из xml-файла.
  • XenMaterial.cs – класс, представляющий материал сцены. Так же содержит статичный метод загрузки файла материалов.
  • XenSceneNode.cs – класс, инкапсулирующий в себе реализацию узла сцены.
  • XenCameraNode.cs, XenDirectionalLight.cs, XenPointLight.cs – наследники класса XenSceneNode, расширенные дополнительными свойствами и методами, присущими камере, и источникам света соответственно.
  • XenVertexPositionTangentSpaceTexture.cs – структура, предназначенная для формирования вершинных буферов геометрии сцены.
  • XenMesh.cs – класс геометрии, содержащий все необходимое для визуализации единицы геометрии (модели).
  • XenMeshPart.cs – класс, содержащий информацию о части модели (материал, и положение в вершинном буфере).
  • XenScene.cs и XenSceme.Loader.cs – две части одного класса XenScene. Данный класс содержит информацию о графе сцены и реализует логику его обновления.

Основной класс игры Game1 является наследником класса Xen.Application, который в свою очередь является аналогом класса Game в Xna. Реализация данного класса представлена далее:

Основной класс игры Game1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Пространства имен Net Framework
using System;
using System.Collections.Generic;
using System.Text;
 
// Пространства имен Xen
using Xen;
using Xen.Camera;
using Xen.Graphics;
using Xen.Ex.Camera;
using Xen.Ex.Graphics;
using Xen.Ex.Graphics2D;
using Xen.Graphics.State;
using Xen.Ex.Graphics.Content;
 
// Пространства имен Xna
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
 
 
namespace XenTutorial
{
// создаем наследника класса Xen.Application
// это альтернатива класса Game в XNA
public class Game1 : Application
{
 
// Главная точка входа в приложение.
static void Main(string[] args)
{
// Создаем экземпляр класса игры и запускаем игру на выполнение
using (Game1 game = new Game1())
{
game.Run();
}
}
 
#region Статичная ссылка на класс игры
private static Game1 singletion;
 
// Предоставляет доступ к экземпляту класса игры,
// запущенной в данный момент из любого места программы.
public static Game1 Singletion
{
get
{
if (Game1.singletion == null)
{
throw new Exception("Игра еще не запущена!");
}
return Game1.singletion;
}
private set { Game1.singletion = value; }
}
#endregion
 
 
// Цель визуализации - экран, по этому создаем экземпляр класса DrawTargetScreen
private DrawTargetScreen drawToScreen;
 
// Переменная, в которую будет загружена сцена
private XenScene scene;
 
// Камера, которой мы будем пользоваться в нашем примере
// в данном классе реализовано управление камерой от первого лица
FirstPersonControlledCamera3D camera;
 
// Переменные определяющие скорость анимации объектов сцены и текущий кадр анимации
public float IpoAnimFPS = 25f;
public float IpoAnimFrame = 0f;
 
 
// Конструктор класса игры
public Game1()
{
// Проверяем, не создана ли игра во второй раз в данном приложении
if (Game1.singletion != null)
{
throw new Exception("Нельзя запускать несколько экземпляров игры в одном приложении!");
}
else
{
// Инициализируем статичную ссылку на класс нашей игры
Game1.singletion = this;
}
}
 
// Переопределенный метод настройки грфического устройства
protected override void SetupGraphicsDeviceManager(GraphicsDeviceManager graphics,
ref RenderTargetUsage presentation)
{
graphics.PreferredBackBufferWidth = 1024;
graphics.PreferredBackBufferHeight = 768;
 
base.SetupGraphicsDeviceManager(graphics, ref presentation);
}
 
// Инициализация игры
protected override void Initialise()
{
// Добавление пути поиска текстур (начиная с корневой папки игры)
TextureLoader.Paths.Add(
System.IO.Path.GetDirectoryName(this.GetType().Assembly.Location));
 
// Создаем и настраиваем камеру
camera = new FirstPersonControlledCamera3D(this.UpdateManager, Vector3.Zero);
camera.MovementSensitivity = new Vector2(0.05f, 0.05f);
camera.Projection.FarClip = 1000;
camera.Projection.NearClip = 0.1f;
camera.Projection.FieldOfView = MathHelper.PiOver4;
 
// Создаем цель визуализации и инициализируем ее созданной камерой и текущим классом
drawToScreen = new DrawTargetScreen(this, camera);
 
// задаем цвет очистки экрана
drawToScreen.ClearBuffer.ClearColour = Color.CornflowerBlue;
}
 
// Переопределенный метод загрузки контента игры
protected override void LoadContent(DrawState state, ContentManager manager)
{
base.LoadContent(state, manager);
 
// Загружаем небесный куб
XenSkyBox skyBox = new XenSkyBox("Sunny", manager);
 
// загружаем материалы сцены
XenMaterial.LoadMaterials("Content/Materials/Level.Materials.xml", manager);
 
// загружаем сцену
scene = XenScene.Load("Content/Levels/Level.xml");
// вызываем метод создания списка визуализируемой геометрии
// его следует вызывать тогда, когда вы изменяете состав объектов сцены
scene.RefreshMeshList();
 
// добавление визуализируемых элементов цели визуализации
drawToScreen.Add(skyBox);
drawToScreen.Add(scene);
 
}
 
// переопределяем метод инициализации ввода пользователя
protected override void InitialisePlayerInput(Xen.Input.PlayerInputCollection playerInput)
{
// говорим, что для первого игрока мышь должна остоваться в центре экрана
// это нужно для нормальной работы камеры от первого лица
playerInput[PlayerIndex.One].InputMapper.CentreMouseToWindow = true;
}
 
// переопределенный метод обновления игры
protected override void Update(UpdateState state)
{
// рассчитываем номер кадра анимации
IpoAnimFrame += state.DeltaTimeSeconds * IpoAnimFPS;
 
// обновляем сцену
scene.Update(state);
 
// обработка выхода их игры
if (state.PlayerInput[PlayerIndex.One].InputState.Buttons.Back.OnPressed)
this.Shutdown();
}
 
// переопределенный метод визуализации
protected override void Draw(DrawState state)
{
// выводим все визуализируемые элементы на экран
drawToScreen.Draw(state);
}
}
}

 

Следующим этапом является проектирование структур данных для загруцки экспортируемой сцены.

Начнем мы проектировать структуры данных, необходимые для загрузки экспортированной сцены с класса материала.

Xen в своем составе имеет альтернативу BasicEffect (эффекту, используемому по умолчанию в XNA для загружаемых моделей), который находится в пространстве имен Xen.Ex.Material. Называется он MaterialShader. Данный шэйдер (эффект) реализует в себе базовую модель освещения, и поддерживает такие возможности как карты нормалей,  большое число направленных и точечных источников света и др., а самое главное он подходит для наших целей.

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

Также он должен обеспечивать хранение информации о источниках света для каждого экземпляра класса, что можно реализовать статическими членами и методами класса.

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

Пришло время исследовать код класса XenMateral. Не хочется разделять код классов на части, поэтому комментарии непосредственно в коде. Для экономии места более не буду приводить импортируемые пространства имен.

код класса XenMateral
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
namespace XenTutorial
{
public class XenMaterial
{
// Публичные поля класса. Конечно их можно реализовать как свойства,
// но в данном случае не вижу в этом смысла
public string Name = "";
public Color Color = Color.LightGray;
public Color SpecularColor = Color.White;
public float SpecularPower = 80f;
public float SpecularIntensity = 1.0f;
public float Ambient = 0.2f;
public Texture2D DiffuseMap;
public Texture2D NormalMap;
// Ключевое поле материала, содержащее ссылку на экземпляр класса MaterialShader
private MaterialShader material;
 
// Публичный конструктор, выполняющий инициализацию материала
public XenMaterial()
{
material = new MaterialShader();
}
 
// Метод, устанавливающий текущий шейдер активным
// После вызова данного метода все операции вывода на экран будут использовать
// данный материал
public void Bind(IShaderSystem state)
{
// Установка параметров материала, сохраненных в данном экземпляре
material.Alpha = Color.A / 255f;
material.DiffuseColour = Color.ToVector3();
material.NormalMap = NormalMap;
material.SpecularColour = SpecularColor.ToVector3() * SpecularIntensity;
material.SpecularPower = SpecularPower;
material.TextureMap = DiffuseMap;
 
// Установка качества фильтрации текстур
material.TextureMapSampler = TextureSamplerState.AnisotropicHighFiltering;
material.NormalMapSampler = TextureSamplerState.AnisotropicLowFiltering;
 
// Установка коллекции источников света из статического поля класса XenMaterial
material.Lights = XenMaterial.Lights;
 
if (material.Lights != null)
{
// Установка общих параметров источникв света
material.Lights.AmbientLightColour = new Vector3(Ambient);
material.Lights.LightingEnabled = true;
}
 
// Установка материала как текущего
material.Bind(state);
 
}
 
 
// Статические члены класса
 
// Коллекция материалов
private static List<XenMaterial> materials = new List<XenMaterial>();
// Материал, возвращаемый тогда, когда в коллекции нет материала с указанным именем
private static XenMaterial noneMaterial;
 
// Коллекция источников света
private static MaterialLightCollection lights = new MaterialLightCollection();
 
// Текущая коллекция источников света (публичное свойство)
public static MaterialLightCollection Lights
{
get { return XenMaterial.lights; }
set { XenMaterial.lights = value; }
}
 
// Коллекция материалов (публичное свойство)
public static List<XenMaterial> Materials
{
get { return XenMaterial.materials; }
}
 
// Функция, возвращающая материал коллекции по имени
public static XenMaterial GetMaterialByName(string name)
{
// Понижаем регистр символов в названии материала для поиска
name = name.ToLower();
 
// Перебираем коллекцию материалов в поисках материала с заданным именем
foreach (XenMaterial mat in Materials)
{
if (mat.Name.ToLower() == name)
{
// Возвращаем найденный материал
return mat;
}
}
 
// Если еще не нинициализировано свойство материала по умолчанию, то создаем его
if (noneMaterial == null)
{
noneMaterial = new XenMaterial();
}
 
// Возвращаем материал по умолчанию
return noneMaterial;
}
 
 
 
// Статический метод для загрузки материалов из экспортированного файла
public static void LoadMaterials(string materialsFileName, ContentManager manager)
{
// Загружаем документ XML
XmlDocument materialsDoc = new XmlDocument();
materialsDoc.Load(materialsFileName);
 
// Перебираем все ноды внутри корневого
foreach (XmlNode node in materialsDoc.DocumentElement)
{
// если имя нода "Material", то загружаем материал
if (node.Name.ToLower() == "material")
{
// Создаем новый экземпляр материала
XenMaterial mat = new XenMaterial();
mat.Name = XmlReadHelper.GetAttributeValue(node, "Name");
 
// Читаем ветку Color для получения информации о цвете материала
string[] vals = XmlReadHelper.GetAttributeValues(
XmlReadHelper.FindChildNode(node, "Color"),
new string[] { "R", "G", "B", "A", "Ambient" }
);
 
mat.Color = new Color(
XmlReadHelper.Val(vals[0]),
XmlReadHelper.Val(vals[1]),
XmlReadHelper.Val(vals[2]),
XmlReadHelper.Val(vals[3]));
mat.Ambient = XmlReadHelper.Val(vals[4]);
 
// Цвет и сила бликовой составляющей материала
vals = XmlReadHelper.GetAttributeValues(
XmlReadHelper.FindChildNode(node, "Specular"),
new string[] { "R", "G", "B", "Power", "Intensity" }
);
 
mat.SpecularColor = new Color(
XmlReadHelper.Val(vals[0]),
XmlReadHelper.Val(vals[1]),
XmlReadHelper.Val(vals[2]));
mat.SpecularPower = XmlReadHelper.Val(vals[3]);
mat.SpecularIntensity = XmlReadHelper.Val(vals[4]);
 
// Читаем список текстур материала (если они есть)
List<string> textures = new List<string>();
 
foreach (XmlNode tex in XmlReadHelper.FindChildNode(node, "Textures"))
{
if (tex.Name.ToLower() == "texture")
{
textures.Add(
System.IO.Path.GetFileNameWithoutExtension(
XmlReadHelper.GetAttributeValue(tex, "Name"))
);
}
}
 
if (textures.Count > 0)
{
// Если текстуры есть, то загружаем первую в качестве диффузной карты
mat.DiffuseMap = TextureLoader.GetTextureByName<Texture2D>(textures[0], manager);
 
}
 
materials.Add(mat);
}
}
}
}
}

 


Следующий класс, необходимый для визуализации геометрии – XenMeshPart. Данный класс будет содержать информацию о части геометрии и ее материале. Реализация его достаточно проста.

XenMeshPart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
namespace XenTutorial
{
public class XenMeshPart
{
// поля класса
int startIndex;
int trianglesCount;
XenMaterial material = null;
 
// Конструктор без параметров
public XenMeshPart(){ }
 
// Конструктор с параметрами
public XenMeshPart(int start, int primCount, XenMaterial mat)
{
startIndex = start;
trianglesCount = primCount;
material = mat;
}
 
// Свойство, обеспечивающее доступ к материалу
public XenMaterial Material
{
get { return material; }
set { material = value; }
}
 
// Начальный индекс в массиве вершин модели
public int StartIndex
{
get { return startIndex; }
set { startIndex = value; }
}
 
// Количество треугольников, входящих в состав данной части модели
public int TrianglesCount
{
get { return trianglesCount; }
set { trianglesCount = value; }
}
}
}

 


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

Но я отвлекся, давайте займемся реализацией своей структуры вершины, которая в себе будет хранить информацию, необходимую для визуализации с использованием карт нормалей.

Здесь Xen нам очень поможет, т.к. для объявления вершинных буферов нет необходимости создавать экземпляр класса VertexDeclaration, т.к. Xen это сделает автоматически.

Давайте взглянем на код:

XenVertexPositionTangentSpaceTexture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace XenTutorial
{
public struct XenVertexPositionTangentSpaceTexture
{
public Vector3 Position;
public Vector3 Normal;
public Vector3 Binormal;
public Vector3 Tangent;
public Vector2 TextureCoordinate;
 
public XenVertexPositionTangentSpaceTexture(
Vector3 position,
Vector3 normal,
Vector3 binormal,
Vector3 tangent,
Vector2 textureCoordinate)
{
this.Position = position;
this.Normal = normal;
this.Binormal = binormal;
this.Tangent = tangent;
this.TextureCoordinate = textureCoordinate;
}
}
}

 


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

XenVertexPositionTangentSpaceTexture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
namespace XenTutorial
{
public struct XenVertexPositionTangentSpaceTexture
{
[Xen.Graphics.VertexElement(VertexElementUsage.Position)]
public Vector3 Position;
 
[Xen.Graphics.VertexElement(VertexElementUsage.Normal)]
public Vector3 Normal;
 
[Xen.Graphics.VertexElement(VertexElementUsage.Binormal)]
public Vector3 Binormal;
 
[Xen.Graphics.VertexElement(VertexElementUsage.Tangent)]
public Vector3 Tangent;
 
[Xen.Graphics.VertexElement(VertexElementUsage.TextureCoordinate)]
public Vector2 TextureCoordinate;
 
public XenVertexPositionTangentSpaceTexture(
Vector3 position,
Vector3 normal,
Vector3 binormal,
Vector3 tangent,
Vector2 textureCoordinate)
{
this.Position = position;
this.Normal = normal;
this.Binormal = binormal;
this.Tangent = tangent;
this.TextureCoordinate = textureCoordinate;
}
}
}

 


Также это поможет избежать некоторых проблем, при использовании обфускации.

Следующим классом, реализацией которого мы займемся, будет XenMesh.

В общем случае данный класс будет альтернативой классу XNA ModelMesh, но данная реализация будет ориентирована на Xen (хотя реализация на XNA будет похожа, но вывод геометрии будет выполнен с использованием большего числа строк кода). Кроме того необходимо сохранить информацию о вершинах и индексный буфер на будущее, когда мы соберемся опрашивать сцену на пересечение с лучем (т.е. реализовать выбор объектов на экране мышкой и др. подобных операций). Для оптимизации так же необходимо создать ограничивающий объем, который позволит не выводить геометрию, если она не попадает в поле зрения камеры, а так же позволит не просчитывать пересечение луча с треугольниками геометрии, если луч не пересекается с ограничивающим объемом. В данной статье я буду использовать BoundingBox (ограничивающий параллелепипед) вместо ограничивающей сферы, т.к. он в большинстве случаев обеспечивает более точное определение  пересечения.

Xen предоставляет универсальный интерфейс IDraw, в котором также должен быть реализован интерфейс ICullable. ICullable содержит определение метода CullTest, в котором необходимо выполнить проверку на отсечение. Данный метод получает объект, в котором реализован интерфейс ICuller, обеспечивающий проверку на отсечение. IDraw содержит определение метода Draw. В данный метод передается переменная типа DrawState, которая в себе содержит все необходимое, для выполнения визуализации.

Теперь давайте перейдем к коду. Я постараюсь прокомментировать только те участки кода, на которые стоит обратить внимание.

XenMesh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
namespace XenTutorial
{
public class XenMesh : IDraw
{
// Мировая матрица, необходимая для вывода геометрии. Расчет данной матрицы
// будет выполнен в другом месте, к ней мы еще вернемся
private Matrix world;
 
// Ссылки на вершинный и индексный буферы, выполненные в виде интерфейсов
private IVertices vertices;
private IIndices indices;
 
// массив индексов
private readonly ushort[] inds;
// Массив координат вершин
private readonly Vector3[] points;
 
// Массив частей геометрии. Необходим для вывода частей с разными материалами
private XenMeshPart[] parts;
 
// Ограничивающий объем геометрии
private BoundingBox bbox;
 
// Переменная, необходимая для сохранения информации пользователя
private object tag;
 
 
// Публичный конструктор, принимающий массив вершин, массив индексов
// и др. необходимую информацию
public XenMesh(ref XenVertexPositionTangentSpaceTexture[] verts, ref ushort[] inds,
ref Vector3[] points, ref XenMeshPart[] parts, ref BoundingBox bbox)
{
this.vertices = new Vertices<XenVertexPositionTangentSpaceTexture>(verts);
this.indices = new Indices<ushort>(inds);
 
this.points = points;
this.inds = inds;
this.bbox = bbox;
this.parts = parts;
}
 
 
// Свойство, обеспечивающие доступ к ограничивающему объему
public BoundingBox BoundingBox
{
get { return bbox; }
private set { bbox = value; }
}
 
// Свойство, обеспечивающие доступ к информации о частях геометрии
public XenMeshPart[] MeshParts
{
get { return parts; }
private set { parts = value; }
}
 
// Мировая матрица геометрии
public Matrix World
{
get { return world; }
set { world = value; }
}
 
// Тэг, место хранения пользовательской информации
public object Tag
{
get { return tag; }
set { tag = value; }
}
 
 
#region Члены IDraw
 
// Наконец метод Draw
public void Draw(DrawState state)
{
// Как я уже говорил, переменная типа DrawState содержит все необходимое
// для визуализации геометрии, включая методы Push и Pop для сохранения и
// влсстановления состояния устройства.
// Следующей строкой осуществляется сохранение текущей мировой матрицы состояния и
// установка матрицы геометрии
state.PushWorldMatrix(ref world);
 
// Перебор каждой части геометрии и визуализация с учетом ее материала
foreach (XenMeshPart part in parts)
{
part.Material.Bind(state);
vertices.Draw(state, indices, PrimitiveType.TriangleList, part.TrianglesCount, part.StartIndex, 0);
}
// Восстановление сохраненной мировой матрицы в состоянии
state.PopWorldMatrix();
}
 
#endregion
 
 
 
#region Члены ICullable
 
// Метод, кроверябщий неометрию на отсечение
public bool CullTest(ICuller culler)
{
// Переменная culler имеет достаточно методов для тестов на отсечение неометрии
return culler.TestBox(bbox.Min, bbox.Max, ref world);
}
 
#endregion
 
}
}

 


Как Вы видите все достаточно просто!


Пришло время реализовать дерево сцены (граф сцены, английское название может быть Scene Graph, Scene Tree и др. синонимы).

Дерево сцены включает в себя иерархию объектов (Node что в переводе узел) и их трансформаций. Каждый объект может иметь две матрицы трансформаций: локальную и мировую. Первая представляет собой трансформации в системе координат объекта-родителя. Вторая – трансформации в системе координат мира. Далее под словом «матрица» я буду подразумевать трансформации.

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

Каждый объект сцены имеет одинаковые общие характеристики (такие как трансформации). Мы с Вами узнали это, когда занимались экспортированием сцены. Следовательно, необходимо реализовать класс, который содержал бы в себе все общие характеристики объектов.

Объекты могут иметь привязанную к ним геометрию, а могут не иметь. Я считаю, что надо дать возможность привязать к любому объекту сцены какую-либо геометрию, как например, к камере можно привязать руку с пистолетом персонажа и т.д.

Первым классом, который будет реализован в этом разделе – XenSceneNode. Затем мы объединим граф сцены и логику, связанную с ним в отдельный класс XenScene, который и будет являться завершающим, необходимым для загрузки объектов сцены из экспортированного файла.

Итак, XenSceneNode.

XenSceneNode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
namespace XenTutorial
{
public class XenSceneNode
{
// Поле с именем объекта сцены, в Blender и др. пакетах трехмерной графики
// каждый объект сцены обязан иметь уникальное имя
private string name;
 
// Матрицы трансформаций, мировая и локальная
private Matrix worldMatrix;
private Matrix localMatrix;
 
// Составляющие локальной трансформации. Эти поля будут использоваться для построения
// локальной матрицы трансформаций.
private Vector3 translation;
private Vector3 rotation;
private Vector3 scale;
 
// Флаг, показывающий необходимость пересчета локальной матрицы трансформаций
// (своего рода некоторая оптимизация)
private bool localMatrixChanged;
 
// Поля, указывающие на наличие родителя и потомков
private XenSceneNode parent;
private List<XenSceneNode> childs;
 
// Список прикрепленных геометрических объектов к данному ноду
private List<XenMesh> meshes;
 
// Список параметров, привязанных к данному объекту
private List<XenSceneNodeParameter> parameters;
 
// Переменная, служащая местом хранения пользовательской информации
private object tag;
 
// Хранилище кривых анимации
private Ipo ipo = new Ipo();
 
// Публичный конструктор, инициализирующий все поля класса.
// Обязательным параметром является имя объекта.
public XenSceneNode(string nodeName)
{
name = nodeName;
 
translation = Vector3.Zero;
rotation = Vector3.Zero;
scale = Vector3.One;
 
worldMatrix = Matrix.Identity;
localMatrix = Matrix.Identity;
localMatrixChanged = false;
 
parent = null;
childs = new List<XenSceneNode>();
meshes = new List<XenMesh>();
 
parameters = new List<XenSceneNodeParameter>();
}
 
public string Name
{
get { return this.name; }
set { this.name = value; }
}
 
public Matrix LocalMatrix
{
get { return this.localMatrix; }
private set { this.localMatrix = value; }
}
 
public Matrix WorldMatrix
{
get { return this.worldMatrix; }
// Воизбежание несоответствия локальных трансформаций установленным значениям
// ограничим возможность установки свойства this.worldMatrix текущим классом
private set { this.worldMatrix = value; }
}
 
// Далее идут три свойства локальных трансформаций, установка которых меняет
// флаг, информирующий о том, что необходимо пересчитать локальную матрицу
public Vector3 Scale
{
get { return this.scale; }
set
{
if (!this.scale.Equals(value))
{
this.localMatrixChanged = true;
}
this.scale = value;
}
}
 
public Vector3 Rotation
{
get { return this.rotation; }
set
{
if (!this.rotation.Equals(value))
{
this.localMatrixChanged = true;
}
this.rotation = value;
}
}
 
public Vector3 Translation
{
get { return this.translation; }
set
{
if (!this.translation.Equals(value))
{
this.localMatrixChanged = true;
}
this.translation = value;
}
}
 
// Коллекция геометрических объектов, привязанных к данному узлу
public List<XenMesh> Meshes
{
get { return meshes; }
}
 
// Коллекция дополнительных параметров, которые можно использовать для построения логики
public List<XenSceneNodeParameter> Parameters
{
get { return parameters; }
set { parameters = value; }
}
 
// Хранилище дополнительной информации в любом формате ;)
public object Tag
{
get { return tag; }
set { tag = value; }
}
 
// Хранилище кривых анимаций
public Ipo Ipo
{
get { return ipo; }
set { ipo = value; }
}
 
 
// Ссылка на родительский узел графа сцены
public XenSceneNode Parent
{
get { return this.parent; }
private set { this.parent = value; }
}
 
 
// Далее свойства и методы, связанные с изменением коллекции потомков (childs)
public XenSceneNode[] Childs
{
get { return this.childs.ToArray(); }
}
 
public int CountChilds
{
get { return this.childs.Count; }
}
 
// Коллекция потомков (childs) закрыта для того, чтобы ссылка на родительский узел была верная
// поэтому реализуем несколько методов работы с потомками
public void AddChild(XenSceneNode child)
{
this.childs.Add(child);
child.Parent = this;
}
public void RemoveChild(XenSceneNode child)
{
this.childs.Remove(child);
child.Parent = null;
}
 
public void ClearChilds()
{
foreach (XenSceneNode child in this.childs)
{
child.parent = null;
}
this.childs.Clear();
}
 
// Метод Update принимает переменную типа UpdateState, которая кроме
// игрового времени содержит ряд дополнительной информации
public virtual void Update(UpdateState state)
{
// получение текущего кадра анимации
float frame = Game1.Singletion.IpoAnimFrame;
 
// Если у данного объекта коллекция кривых анимации
if (ipo != null)
{
// сначала получаем текущие значения
// т.к. для анимации перемещения могут присутствовать кривые
// не для всех каналов, то проверяем из по отдельности
Vector3 trans = this.Translation;
if(Ipo.GetIpo(IpoType.LocX)!= null)
{
trans.X = Ipo.GetIpo(IpoType.LocX).GetValueByFrameNumber(frame);
}
if (Ipo.GetIpo(IpoType.LocY) != null)
{
trans.Y = Ipo.GetIpo(IpoType.LocY).GetValueByFrameNumber(frame);
}
if (Ipo.GetIpo(IpoType.LocZ) != null)
{
trans.Z = Ipo.GetIpo(IpoType.LocZ).GetValueByFrameNumber(frame);
}
// устанавливаем новое значение как текущее
this.Translation = trans;
 
 
// делаем тоже самое для анимации вращения и масштабирования
 
Vector3 rot = this.Rotation;
if (Ipo.GetIpo(IpoType.RotX) != null)
{
rot.X = Ipo.GetIpo(IpoType.RotX).GetValueByFrameNumber(frame);
}
if (Ipo.GetIpo(IpoType.RotY) != null)
{
rot.Y = Ipo.GetIpo(IpoType.RotY).GetValueByFrameNumber(frame);
}
if (Ipo.GetIpo(IpoType.RotZ) != null)
{
rot.Z = Ipo.GetIpo(IpoType.RotZ).GetValueByFrameNumber(frame);
}
this.Rotation = rot;
 
 
Vector3 scl = this.Scale;
if (Ipo.GetIpo(IpoType.ScaleX) != null)
{
scl.X = Ipo.GetIpo(IpoType.ScaleX).GetValueByFrameNumber(frame);
}
if (Ipo.GetIpo(IpoType.ScaleY) != null)
{
scl.Y = Ipo.GetIpo(IpoType.ScaleY).GetValueByFrameNumber(frame);
}
if (Ipo.GetIpo(IpoType.ScaleZ) != null)
{
scl.Z = Ipo.GetIpo(IpoType.ScaleZ).GetValueByFrameNumber(frame);
}
this.Scale = scl;
 
}
 
 
// Если локальная матрица изменилась, то выполняем пересчет
// и сбрасываем флаг
if (this.localMatrixChanged)
{
// Сначала масштаб, потом вращение и наконец перемещение
this.localMatrix = Matrix.CreateScale(this.Scale) *
Matrix.CreateFromYawPitchRoll(
this.Rotation.Y,
this.Rotation.X,
this.Rotation.Z) *
Matrix.CreateTranslation(this.Translation);
 
this.localMatrixChanged = false;
}
 
// В зависимости от того, есть ли родитель у данного нода,
// выполняем расчет мировой матрицы
if (Parent != null)
{
this.worldMatrix = this.parent.WorldMatrix * this.LocalMatrix;
}
else
{
// Как я и говорил, у корневых нодов мировая матрица равна локальной
this.worldMatrix = this.LocalMatrix;
}
}
}
 
 
// Небольшая структура, представляющая собой информацию о дополнительном параметре узла сцены
public struct XenSceneNodeParameter
{
public string Name;
public string Value;
 
public XenSceneNodeParameter(string name, string value)
{
Name = name;
Value = value;
}
}
}

 


Реализация данного класса не составила у нас большого труда, т.к. нет никаких сложных конструкций. Наследниками данного класса являются XenCameraNode, XenDirectionalLight и XenPointLight. Описание данных классов присутствует в исходном коде и Вам не составит труда их разобрать самостоятельно.

Класс XenScene так же не потребует от нас много усилий по реализации. Здесь мы посмотрим возможности Xen по сортировке геометрических объектов перед выводом. Сортировка необходима для обеспечения правильного вывода прозрачной геометрии.

Первая часть класса, без части загрузки сцены приведена далее:

XenScene
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
namespace XenTutorial
{
public partial class XenScene : IDraw
{
// объявление экземпляра класса, сортирующего геометрию по глубине
// данный сортировщик основан на CullTest'е и может сортировать объекты раз в
// определенное число выводов на экран. В нашем случае мы будем использовать этот
// объект с параметрами по умолчанию
private DepthDrawSorter objectsToDraw = new DepthDrawSorter(DepthSortMode.BackToFront);
// коллекция источников света данной сцены специализированная для MaterialShader
private MaterialLightCollection lights = new MaterialLightCollection();
 
// корень графа сцены
private XenSceneNode rootSceneNode;
 
// источники света данной сцены
private List<XenPointLightNode> plights = new List<XenPointLightNode>();
private List<XenDirectionalLightNode> dlights = new List<XenDirectionalLightNode>();
 
 
// конструктор сцены получает корневой узел
public XenScene(XenSceneNode root)
{
rootSceneNode = root;
objectsToDraw.SortDelayFrameCount = 10;
}
 
// метод обновления сцены
public void Update(UpdateState state)
{
// очищаем коллекции
lights.RemoveAllLights();
plights.Clear();
dlights.Clear();
 
// вызываем рекурсивное обновление узлов сцены, начиная с корневого
UpdateNode(rootSceneNode, state);
}
 
// метод, выполняющий обновление узла сцены
protected void UpdateNode(XenSceneNode node, UpdateState state)
{
// вызов метода обновления узла
node.Update(state);
 
 
// заполнение всех коллекций источников света
if (node is XenDirectionalLightNode)
{
XenDirectionalLightNode dl = (node as XenDirectionalLightNode);
dlights.Add(dl);
lights.AddDirectionalLight(false, -dl.Direction, dl.Color);
}
else if (node is XenPointLightNode)
{
XenPointLightNode pl = (node as XenPointLightNode);
plights.Add(pl);
lights.AddPointLight(true, pl.Translation, pl.Range, pl.Color);
}
 
 
Matrix world = node.WorldMatrix;
// установка мировой матрицы для всей геометрии
foreach (XenMesh mesh in node.Meshes)
{
mesh.World = world;
}
 
// выполнение обновления всех потомков
foreach (XenSceneNode child in node.Childs)
{
UpdateNode(child, state);
}
}
 
// обход дерева сцены и заполнение коллекции геометрии для вывода
public void RefreshMeshList()
{
objectsToDraw.Clear();
RefreshMeshNode(rootSceneNode);
}
 
// вункция рекурсивного объхода дерева сцены
protected void RefreshMeshNode(XenSceneNode node)
{
 
foreach (XenMesh mesh in node.Meshes)
{
objectsToDraw.Add(mesh);
mesh.Tag = node;
}
 
foreach (XenSceneNode child in node.Childs)
{
RefreshMeshNode(child);
}
}
 
// вывод геометрии сцены в цель визуализации
public void Draw(DrawState state)
{
// сохранение старой коллекции источников света, которые были установлены ранее
MaterialLightCollection oldLights = XenMaterial.Lights;
// установка текущей коллекции источников света
XenMaterial.Lights = lights;
 
// вывод сортированной геометрии
objectsToDraw.Draw(state);
 
// восстановление старой коллекции источников света
XenMaterial.Lights = oldLights;
}
 
public XenSceneNode RootSceneNode
{
get { return rootSceneNode; }
private set { rootSceneNode = value; }
}
 
 
 
#region Члены ICullable
 
// всегда визуализируем сцену
// те объекты, которые не попадают в кадр отсечет сам DepthDrawSorter
public bool CullTest(ICuller culler)
{
return true;
}
 
#endregion
}
}

 


Далее рассмотрим набор классов, которые являются частным воспроизведением классов анимации Blender для нашей задачи (на самом деле я не копался в исходниках Blender’а, я просто предположил, что кривые анимации в нем выполнены с использованием кривых Безье, и я угадал).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
namespace XenTutorial
{
// Класс, хранящий информацию об узле кривой Безье
public class BezTriple
{
Vector2
handle1,
point,
handle2;
 
public BezTriple()
{
Handle1 = Vector2.Zero;
Point = Vector2.Zero;
Handle2 = Vector2.Zero;
}
 
public BezTriple(Vector2 h1, Vector2 p, Vector2 h2)
{
Handle1 = h1;
Point = p;
Handle2 = h2;
}
 
public Vector2 Handle1
{
get { return handle1; }
set { handle1 = value; }
}
 
public Vector2 Point
{
get { return point; }
set { point = value; }
}
 
public Vector2 Handle2
{
get { return handle2; }
set { handle2 = value; }
}
}
}
 
 
namespace XenTutorial
{
// Перечисление типов кривых Ipo, загружаемых из файла сцены.
public enum IpoType
{
//Object Ipo
LocX = 0, LocY, LocZ,
RotX, RotY, RotZ,
ScaleX, ScaleY, ScaleZ,
//Camera Ipo
Lens, FarClip, NearClip,
//Lamp Ipo
Energ, R, G, B, Dist,
//Action Ipo (for Armature)
//LocX, LocY, LocZ
SizeX, SizeY, SizeZ,
QuatX, QuatY, QuatZ, QuatW
}
}
 
 
namespace XenTutorial
{
// Класс кривой анимации
public class IpoCurve : ICollection<BezTriple>
{
// имя кривой
string name;
 
// узлы кривой
List<BezTriple> points;
 
// точки начала и конца кривой
Vector2 start, end;
 
// Зациклина ли кривая
// здесь мы не реализуем все типы воспроизведения циклов анимации
// выбираем самый простой – при переходе времени за промежуток анимации
// повторяем анимацию сначала
bool ciclic = false;
 
public bool Ciclic
{
get { return ciclic; }
set { ciclic = value; }
}
 
// Далее два конструктора класса
public IpoCurve(string curveName)
{
Name = curveName;
points = new List<BezTriple>();
}
 
public IpoCurve(string curveName, BezTriple[] points)
{
Name = curveName;
this.points = new List<BezTriple>();
foreach (BezTriple point in points)
{
this.Add(point);
}
}
 
// функция получения интерполированного значения кубической кривой Безье между двумя узлами
private Vector2 GetValue(float t, BezTriple p1, BezTriple p2)
{
// вычисление положения на кубической кривой Безье

return (float)System.Math.Pow((1.0f - t), 3) * p1.Point +
3f * (t * (float)System.Math.Pow((1.0f - t), 2) * p1.Handle2 +
t * t * (1.0f - t) * p2.Handle1) +
(float)System.Math.Pow(t, 3) * p2.Point;
}
 
// функция получения значения кривой по номеру кадра анимации
public float GetValueByFrameNumber(float frame)
{
float len = End.X - Start.X;
// если промежуток анимации по времени равен нулю (например когда
// создан всего один ключевой узел), то возвращаем значение в первом кадре
if (len == 0.0f)
{
return Start.Y;
}
// если кривая зациклена, то рассчитываем значение времени внутри промежутка анимации
if (Ciclic)
{
if (len > 0.0f)
{
if (frame < Start.X)
{
float over = (Start.X - frame) / len;
frame += len * (float)System.Math.Floor(over);
}
else if (frame > End.X)
{
float over = (frame - End.X) / len;
frame -= len * (float)System.Math.Ceiling(over);
}
}
}
 
// поиск нужной пакы ключевых кадров и интерполяция с использованием кубической
// кривой Безье
if (frame <= Start.X)
{
return Start.Y;
}
else if (frame >= End.X)
{
return End.Y;
}
else
{
for (int i = 1; i < points.Count; i++)
{
if ((frame >= points[i - 1].Point.X) && (frame <= points[i].Point.X))
{
float t = (frame - points[i - 1].Point.X) / (points[i].Point.X - points[i - 1].Point.X);
return GetValue(t, points[i - 1], points[i]).Y;
}
}
}
return 0f;
}
 
public string Name
{
get { return name; }
set { name = value; }
}
 
 
public Vector2 End
{
get { return end; }
private set { end = value; }
}
 
public Vector2 Start
{
get { return start; }
private set { start = value; }
}
 
 
// переопределение начала и конца анимации при добавлении или удалении
// узлов из списка
private void RefreshEndStart()
{
if (points.Count > 0)
{
Start = points[0].Point;
End = points[points.Count - 1].Point;
}
else
{
Start = new Vector2(0f);
End = new Vector2(0f);
}
}
 
// реализация коллекции узлов анимации с помощью ICollection
// не ривожу из экономии места
}
}
 
 
namespace XenTutorial
{
public class Ipo
{
// массив кривых анимации
private IpoCurve[] curves = null;
 
public Ipo()
{
// число кривых в массиве рассчитывается из кол-ва элементов перечисления IpoType
curves = new IpoCurve[Enum.GetNames(typeof(IpoType)).Length];
}
 
// установка кривой в коллекцию по индексу, хранящемуся в имени перечисления
public void SetIpo(IpoType type, IpoCurve curve)
{
curves[(int)type] = curve;
}
 
// установка кривой в коллекцию имени кривой
public void SetIpo(string name, IpoCurve curve)
{
string [] names = Enum.GetNames(typeof(IpoType));
for (int i = 0; i < names.Length; i++ )
{
if (names[i] == name)
{
curves[i] = curve;
}
}
}
 
// получение кривой из коллекции
public IpoCurve GetIpo(IpoType type)
{
return curves[(int)type];
}
 
// Метод проверки имени кривой на предмет наличия в перечислении
public static bool IsSupportIpoName(string name)
{
foreach (string n in Enum.GetNames(typeof(IpoType)))
{
if (n == name)
{
return true;
}
}
return false;
}
}
}

 


Итак мы подошли к финальной части нашей статьи, в который мы наконец реализуем загрузку сцены в игру. Реализация второй части класса XenScene, находящаяся в файле XenScene.Loader.cs представлена далее (в данном классе используются методы класса XmlReadHelper, который не приведен в самой статье, его описание Вы найдете в исходном коде, прилагаемом к статье):

XenScene.Loader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
namespace XenTutorial
{
public partial class XenScene : IDraw
{
// Метод загрузки сцены их Xml-файла
// Прежде чем его использовать загрузите материалы сцены
public static XenScene Load(string sceneFileName)
{
// Загружаем документ в память
XmlDocument levelDoc = new XmlDocument();
levelDoc.Load(sceneFileName);
 
// Создаем корневой элемент сцены
XenSceneNode Root = new XenSceneNode("Root");
Root.Scale = new Vector3(1, 1, 1);
Root.Name = "Root";
 
// Загружаем все ноды сцены
foreach (XmlNode node in levelDoc.DocumentElement)
{
if (node.Name.ToLower() == "node")
{
ReadSceneNode(node, Root);
}
}
 
// Создаем сцену и передаем ей корневой элемент
XenScene scene = new XenScene(Root);
 
// освобождаем документ
levelDoc = null;
GC.Collect();
 
// возвращаем сцену
return scene;
}
 
 
// Функция чтения узла сцены
public static void ReadSceneNode(XmlNode node, XenSceneNode parent)
{
// читаем атрибуты с именем и типом узла сцены
string nodeName = XmlReadHelper.GetAttributeValue(node, "Name");
string nodeType = XmlReadHelper.GetAttributeValue(node, "Type");
 
XenSceneNode snode = null;
string[] vals;
 
// с учетом типа узла читаем специфические данные о нем
switch (nodeType.ToLower().Trim())
{
case "camera": // Загрузка камеры
vals = XmlReadHelper.GetAttributeValues(
node, new string[] { "Lens", "ClipStart", "ClipEnd" });
 
// Создаем узел камеры
XenCameraNode cam;
cam = new XenCameraNode(
nodeName,
Matrix.CreatePerspectiveFieldOfView(
XmlReadHelper.Val(vals[0]),
4f / 3f,
XmlReadHelper.Val(vals[1]),
XmlReadHelper.Val(vals[2]))
);
 
snode = cam;
break;
case "mesh": // Загрузка геометрии
XenMesh mesh;
 
//Создаем простой узел
snode = new XenSceneNode(nodeName);
 
// выполняем поиск наличия данных о геометрии
XmlNode meshNode = XmlReadHelper.FindChildNode(node, "Mesh");
if (meshNode == null)
{
break;
}
// Если данные о геометрии есть, то ищем узлы, стодержащие данные о вершинах
// и о частях модели
XmlNode verticesNode = XmlReadHelper.FindChildNode(meshNode, "Vertices");
List<XmlNode> meshParts = XmlReadHelper.FindChildNodes(meshNode, "MeshPart");
List<Vector3> verticesPosition = new List<Vector3>();
List<ushort> indices = new List<ushort>();
 
List<XenMeshPart> parts = new List<XenMeshPart>();
 
// Список врешин, на основе которого будет построен вершинный буфер
List<XenVertexPositionTangentSpaceTexture> vertices =
new List<XenVertexPositionTangentSpaceTexture>();
 
// читаем позиции вершин
for (int i = 0; i < verticesNode.ChildNodes.Count; i++)
{
XmlNode n = verticesNode.ChildNodes[i];
if (n.Name.ToLower() == "vert")
{
vals = XmlReadHelper.GetAttributeValues(n, new string[] { "X", "Y", "Z" });
verticesPosition.Add(XmlReadHelper.ReadVector3(n, "X", "Y", "Z" ));
}
}
 
// читаем данные о частях геометрии
for (int i = 0; i < meshParts.Count; i++)
{
foreach (XmlNode face in meshParts[i].ChildNodes)
{
for (int j = 0; j < face.ChildNodes.Count; j++)
{
if (face.ChildNodes[j].Name.ToLower() == "v")
{
// добавляем вершину в список вершин с одновременным
// добавлением индекса вершины в коллекцию
indices.Add(
AddVertexToList(
ref vertices,
ref verticesPosition,
verticesPosition[int.Parse(XmlReadHelper.GetAttributeValue(face.ChildNodes[j], "Index"))],
XmlReadHelper.ReadVector3(face.ChildNodes[j], "NX", "NY", "NZ"),
XmlReadHelper.ReadVector3(face.ChildNodes[j], "TX", "TY", "TZ"),
XmlReadHelper.ReadVector2(face.ChildNodes[j], "UVX", "UVY")));
}
}
}
// создаем эеземпляр части геометрии и рассчитываем для нее величины
XenMeshPart mp = new XenMeshPart();
parts.Add(mp);
 
if (parts.Count == 1)
{
mp.StartIndex = 0;
mp.TrianglesCount = indices.Count / 3;
}
else
{
mp.StartIndex =
parts[i - 1].StartIndex + parts[i - 1].TrianglesCount * 3;
mp.TrianglesCount = indices.Count / 3 - parts[i].StartIndex / 3;
}
// получаем материал по имени из коллекции загруженных материалов
mp.Material = XenMaterial.GetMaterialByName(
XmlReadHelper.GetAttributeValue(meshParts[i], "Material"));
}
 
// Удаляем "пустые" части геометрии
// Это необходимо сделать, т.к. Blender иногда сохраняет ссылку на
// пустую геометрию с назначенным материалом
// Такая ситуация может произойти при редактировании геометрии моделером
for (int i = parts.Count - 1; i >= 0; i--)
{
if (parts[i].TrianglesCount == 0)
{
parts.RemoveAt(i);
}
}
 
// Расчет ограничивающего бокса
BoundingBox bbox = BoundingBox.CreateFromPoints(verticesPosition);
 
ushort[] ins = indices.ToArray();
 
XenVertexPositionTangentSpaceTexture[] verts = vertices.ToArray();
 
Vector3[] pnts = verticesPosition.ToArray();
 
// создание экземпляра геометрии
mesh = new XenMesh(ref verts, ref ins, ref pnts, ref parts, ref bbox);
// добавление геометрии узлу сцены
snode.Meshes.Add(mesh);
 
break;
case "empty": // Загрузка объекта пустышки
snode = new XenSceneNode(nodeName);
 
break;
case "lamp": // Загрузка источников света
 
vals = XmlReadHelper.GetAttributeValues(
node, new string[] { "LampType", "Dist", "Energy" });
 
XmlNode colorNode = XmlReadHelper.FindChildNode(node, "Color");
Vector3 color = XmlReadHelper.ReadVector3(colorNode, "X", "Y", "Z");
 
if (vals[0].ToLower() == "sun") // направленный источник света
{
XenDirectionalLightNode light =
new XenDirectionalLightNode(nodeName, new Color(color));
snode = light;
}
else // все остальные источники считаем точечными
{
XenPointLightNode light =
new XenPointLightNode(nodeName, new Color(color));
light.Range = XmlReadHelper.Val(vals[1]);
light.Intensity = XmlReadHelper.Val(vals[2]);
snode = light;
}
 
break;
default: // другие типы объектов сцены не поддерживаем
return;
}
 
if (snode == null)
{
return;
}
 
// загружаем кривые анимации
XmlNode ipoNode = XmlReadHelper.FindChildNode(node, "Ipo");
snode.Ipo = new Ipo();
 
foreach (XmlNode ipo in ipoNode.ChildNodes)
{
// если кривая анимации поддерживается
if (Ipo.IsSupportIpoName(ipo.Name))
{
// определяем тип кривой анимации
IpoType type = (IpoType)Enum.Parse(typeof(IpoType), ipo.Name);
 
// создаем коллекцию для ключевых узлов кривой анимации
List<BezTriple> points = new List<BezTriple>();
 
// читаем все узлы анимации
List<XmlNode> pointNodes = XmlReadHelper.FindChildNodes(ipo, "Point");
foreach (XmlNode point in pointNodes)
{
XmlNode h1, p, h2;
h1 = XmlReadHelper.FindChildNode(point, "h1");
p = XmlReadHelper.FindChildNode(point, "p");
h2 = XmlReadHelper.FindChildNode(point, "h2");
 
points.Add(new BezTriple(
XmlReadHelper.ReadVector2(h1, "X", "Y"),
XmlReadHelper.ReadVector2(p, "X", "Y"),
XmlReadHelper.ReadVector2(h2, "X", "Y")));
}
// создаем кривую на основе узлов
IpoCurve curve = new IpoCurve(ipo.Name, points.ToArray());
 
// проверяем, требуется ли повторять анимацию по окончании
if (XmlReadHelper.GetAttributeValue(ipo, "Extend").ToUpper() == "CYCLIC")
{
curve.Ciclic = true;
}
 
// добавляем загруженную кривую в коллекцию кривых объекта
snode.Ipo.SetIpo(type, curve);
}
}
 
// Загружаем дополнительные параметры узлов сцены
List<XenSceneNodeParameter> props = new List<XenSceneNodeParameter>();
 
XmlNode propertiesNode = XmlReadHelper.FindChildNode(node, "Properties");
 
if (propertiesNode != null)
{
XenSceneNodeParameter prop;
foreach (XmlNode property in propertiesNode.ChildNodes)
{
if (property.Name.ToLower() == "property")
{
// читаем имя и значение свойства
vals = XmlReadHelper.GetAttributeValues(
node, new string[] { "Name", "Value" });
prop = new XenSceneNodeParameter();
prop.Name = vals[0];
prop.Value = vals[1];
props.Add(prop);
 
}
}
}
 
snode.Parameters = props;
 
 
// загружаем начальные трансформации объекта сцены (локальные)
XmlNode transformsNode = XmlReadHelper.FindChildNode(node, "Transforms");
 
if (transformsNode != null)
{
XmlNode rotNode = XmlReadHelper.FindChildNode(transformsNode, "Rot");
XmlNode posNode = XmlReadHelper.FindChildNode(transformsNode, "Pos");
XmlNode sclNode = XmlReadHelper.FindChildNode(transformsNode, "Scl");
 
snode.Rotation = XmlReadHelper.ReadVector3(rotNode, "X", "Y", "Z");
snode.Translation = XmlReadHelper.ReadVector3(posNode, "X", "Y", "Z");
snode.Scale = XmlReadHelper.ReadVector3(sclNode, "X", "Y", "Z");
}
 
snode.Name = nodeName;
 
// добавляем текущий узел дерева сцены к родителю
parent.AddChild(snode);
 
 
// загружаем все дочерние узлы дерева сцены
foreach (XmlNode child in node.ChildNodes)
{
if (child.Name.ToLower() == "node")
{
ReadSceneNode(child, snode);
}
}
}
 
// метод добавляющий в коллекцию вершин геометрии новую вершину и возвращающий ее индекс
public static ushort AddVertexToList(
ref List<XenVertexPositionTangentSpaceTexture> vertsList,
ref List<Vector3> positions,
Vector3 pos, Vector3 norm, Vector3 tan, Vector2 uv)
{
// Считаем бинормаль
Vector3 binormal = Vector3.Cross(tan, norm);
binormal.Normalize();
binormal.X = (float)System.Math.Round(binormal.X, 4);
binormal.Y = (float)System.Math.Round(binormal.Y, 4);
binormal.Z = (float)System.Math.Round(binormal.Z, 4);
 
// Добавляемый вершину
XenVertexPositionTangentSpaceTexture vertex =
new XenVertexPositionTangentSpaceTexture();
vertex.Position = pos;
vertex.Normal = norm;
vertex.Binormal = binormal;
vertex.Tangent = tan;
vertex.TextureCoordinate = uv;
 
//// Некоторая оптимизация, правда может сильно повлиять на скорость загрузки
//XenVertexPositionTangentSpaceTexture tmp;
//// Ищем такой же вертекс в списке
//for (int i = 0; i < vertsList.Count; i++)
//{
// tmp = vertsList;
// if (CompareVector3(tmp.Position, vertex.Position) &&
// CompareVector3(tmp.Normal, vertex.Normal) &&
// CompareVector3(tmp.Tangent, vertex.Tangent) &&
// CompareVector2(tmp.TextureCoordinate, vertex.TextureCoordinate))
// {
// return (ushort)i; // такой вертекс уже существует, возвращаем его индекс
// }
//}
 
// Добавляем новую вершину, т.к. такая в списке не найдена (если включена оптимизация)
vertsList.Add(vertex);
 
// Возвращаем индекс последнего элемента списка
return (ushort)(vertsList.Count - 1);
}
 
// методы сравнения векторов
public static bool CompareVector3(Vector3 v1, Vector3 v2)
{
return (Math.Abs(v1.X - v2.X) < 0.001) && (Math.Abs(v1.Y - v2.Y) < 0.001) && (Math.Abs(v1.Z - v2.Z) < 0.001);
}
 
public static bool CompareVector2(Vector2 v1, Vector2 v2)
{
return (Math.Abs(v1.X - v2.X) < 0.001) && (Math.Abs(v1.Y - v2.Y) < 0.001);
}
}
}

 


Наконец мы справились с нашей задачей! Поздравляю всех, кто дошел до конца!
Давайте посмотрим на результаты:




По моему неплохо :).


Некоторые выводы:

  • Т.к. скорость загрузки модели у нас получилась довольно медленная, я рекомендую использовать бинарный формат. Для этого нет необходимости переписывать экспортер. Достаточно написать утилиту на C# которая бы конвертировала файл сцены в бинарный формат.
  • Возможно, в скрипте экспорта не учтены все моменты, так что при экспорте ваших собственных сцен могут возникнуть проблемы.
  • В статье могут присутствовать очепятки и неточности, если Вы меня поправите, то буду только рад.



Если у Вас еще есть силы, то настало время для бонусной части данной статьи.

Использование шейдеров в Xen на примере создания SkyBox’а.

Специально на закуску я хочу продемонстрировать возможности Xen по работе с шейдерами.

Теоретическая часть.

В составе проекта Xen разрабатывается специальная утилита XenFX, которая позволяет на основе файла эффекта создавать класс на языке C#. В результате все файлы *.fx перемещаются из проекта контента в основной проект и при компиляции все шейдеры сохраняются в виде отдельных классов внутри *.exe. Все параметры шейдеров становятся полями сгенерированных классов.

Утилита XenFX генерирует отдельный класс для каждой техники шейдера, который можно использовать в дальнейшем также как и стандартный шейдер MaterialShader.

Каждый сгенерированный класс, кроме параметров шейдера, так же имеет метод Bind, который устанавливает шейдер как текущий, и все что будет выведено на экран будет использовать данный шейдер. Метод Bind так же устанавливает автоматически параметры для различных семантик, определенных для параметров шейдера. В качестве семантик можно использовать любые комбинации WORLD, VIEW и  PROJECTION в указанном порядке (например WORLDVIEWPROJECTION – верно, VIEWWORLDPROJECTION – не верно)  к которым так же можно добавлять суффиксы 'TRANSPOSE' или 'INVERSE'.  Кром е того существует поддержка некоторого количества не матричных семантик. Все они приведены в следующем примере:

поддержка не матричных семантик
1
2
3
4
5
6
7
8
9
float4x4 worldViewProj : WORLDVIEWPROJECTION;  // пример семантики WORLDVIEWPROJECTION
 
// дополнительные семантики
float2 windowSize : WINDOWSIZE; // Размер в пикселях текущей цели визуализации
float2 cameraFov : CAMERAFOV; // горизонтальный и вертикальный FOV камеры
float2 cameraFovTan : CAMERAFOVTANGENT;
float2 cameraNearFar : CAMERANEARFAR;
float3 viewPoint : VIEWPOINT; // позиция камеры
float3 viewDirection : VIEWDIRECTION;

 


Существует специальная семантика GLOBAL с которой можно определить параметры шейдеров, имеющих одинаковое название (название параметра). И устанавливать во всех шейдерах автоматически одинаковый параметр с помощью метода DrawState.SetShaderGlobal(). Например можно во всех шейдерах, зависящих от времени объявить параметр

float Time : GLOBAL;

И при каждом вызове отрисовки игры устанавливать этот параметры для всех шейдеров следующим образом:

1
2
3
4
5
6
7
8
protected override void Draw(DrawState state)
{
// установка глобального параметра
state.SetShaderGlobal("Time", state.TotalTimeSeconds);
 
//отрисовка сцены
drawToScreen.Draw(state);
}

 


Данный параметр будет устанавливаться в каждом шейдере, при вызове метода Bind().

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

//CompilerOptions = InternalClass

Доступны следующие флаги компиляции: NoPreShader, InternalClass, ParentNamespace, AvoidFlowControl, PreferFlowControl, PartialPrecision, DefinePlatform

И в заключение данная утилита поддерживает команды препроцессора в виде:


1
2
3
4
5
#ifdef XBOX360
...
#else
...
#endif

 


Практическая часть.

Ну вот, с теорией покончено, давайте приступим к практике.
Для реализации скайбокса нам понадобится следующий шейдер (описывать его нет нужны, он очень простой):

//CompilerOptions = ParentNamespace, InternalClass


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
float4x4 WVP;
 
texture skyBoxCubeMap;
 
samplerCUBE skyBoxCubeMapSampler = sampler_state
{
Texture = <skyBoxCubeMap>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
 
AddressU = Clamp;
AddressV = Clamp;
};
 
 
struct VS_INPUT
{
float4 position: POSITION;
};
 
struct VS_OUTPUT
{
float4 position: POSITION;
float3 texcoord0: TEXCOORD0;
};
 
VS_OUTPUT VShader( VS_INPUT input )
{
VS_OUTPUT output;
 
float4 pos = input.position;
 
output.position = mul(pos, WVP);
output.position.z = output.position.w;
 
output.texcoord0 = input.position;
 
return output;
}
 
float4 PShader( VS_OUTPUT input ) : COLOR0
{
float4 outColor = 1.0;
 
outColor = texCUBE(skyBoxCubeMapSampler, input.texcoord0);
 
return outColor;
}
 
technique SkyBoxRendererEffect
{
pass pass0
{
VertexShader = compile vs_1_1 VShader();
PixelShader = compile ps_1_1 PShader();
}
}

 


Создадим для шейдеров папку Effects в нашем проекте игры (не в проекте контента!). Добавим туда новый файл SkyBox.fx с содержанием, приведенным выше. Выберем этот файл и установим для него в параметрах специальную утилиту XenFX (набрать вручную):



Если данная утилита зарегистрирована в системе (а она регистрируется при построении Xen с использованием файла «xen prebuild.bat» входящего в состав поставки). То для файла эффекта будет создан дочерний файл SkyBox.fx.cs, который и является сгенерированным кодом шейдера. Давайте посмотрим на структуру методов и свойств сгенерированного объекта:



Нам интересны два свойства (SkyBoxTexture и WVP) и метод Bind() на основе который мы напишем класс скайбокса. Его реализация далее:

XenSkyBox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
namespace XenTutorial
{
public class XenSkyBox : IDraw
{
// Вершинный и индексный буфферы
private IVertices vertices;
private IIndices indices;
// Ссылка на кубическую текстуру
private TextureCube texture;
// размер бокса
private float size = 10.0f;
 
// Конструктор принимает имя текстры и ссылку на менеджер контента
public XenSkyBox(string texName, ContentManager manager)
{
texture = TextureLoader.GetTextureByName<TextureCube>(texName, manager);
 
// Используем стандартную структуру XNA для создания вершинного буфера
VertexPositionNormalTexture[] cubeVertices =
new VertexPositionNormalTexture[36];
 
// Положения вершин
Vector3 topLeftFront = new Vector3(-size, size, size);
Vector3 bottomLeftFront = new Vector3(-size, -size, size);
Vector3 topRightFront = new Vector3(size, size, size);
Vector3 bottomRightFront = new Vector3(size, -size, size);
Vector3 topLeftBack = new Vector3(-size, size, -size);
Vector3 topRightBack = new Vector3(size, size, -size);
Vector3 bottomLeftBack = new Vector3(-size, -size, -size);
Vector3 bottomRightBack = new Vector3(size, -size, -size);
 
// текстурные координаты
Vector2 textureTopLeft = new Vector2(0.0f, 0.0f);
Vector2 textureTopRight = new Vector2(1.0f, 0.0f);
Vector2 textureBottomLeft = new Vector2(0.0f, 1.0f);
Vector2 textureBottomRight = new Vector2(1.0f, 1.0f);
 
// нормали граней
Vector3 frontNormal = new Vector3(0.0f, 0.0f, 1.0f);
Vector3 backNormal = new Vector3(0.0f, 0.0f, -1.0f);
Vector3 topNormal = new Vector3(0.0f, 1.0f, 0.0f);
Vector3 bottomNormal = new Vector3(0.0f, -1.0f, 0.0f);
Vector3 leftNormal = new Vector3(-1.0f, 0.0f, 0.0f);
Vector3 rightNormal = new Vector3(1.0f, 0.0f, 0.0f);
 
// передняя грань
cubeVertices[0] =
new VertexPositionNormalTexture(
topLeftFront, frontNormal, textureTopLeft);
cubeVertices[1] =
new VertexPositionNormalTexture(
bottomLeftFront, frontNormal, textureBottomLeft);
cubeVertices[2] =
new VertexPositionNormalTexture(
topRightFront, frontNormal, textureTopRight);
cubeVertices[3] =
new VertexPositionNormalTexture(
bottomLeftFront, frontNormal, textureBottomLeft);
cubeVertices[4] =
new VertexPositionNormalTexture(
bottomRightFront, frontNormal, textureBottomRight);
cubeVertices[5] =
new VertexPositionNormalTexture(
topRightFront, frontNormal, textureTopRight);
 
// задняя грань
cubeVertices[6] =
new VertexPositionNormalTexture(
topLeftBack, backNormal, textureTopRight);
cubeVertices[7] =
new VertexPositionNormalTexture(
topRightBack, backNormal, textureTopLeft);
cubeVertices[8] =
new VertexPositionNormalTexture(
bottomLeftBack, backNormal, textureBottomRight);
cubeVertices[9] =
new VertexPositionNormalTexture(
bottomLeftBack, backNormal, textureBottomRight);
cubeVertices[10] =
new VertexPositionNormalTexture(
topRightBack, backNormal, textureTopLeft);
cubeVertices[11] =
new VertexPositionNormalTexture(
bottomRightBack, backNormal, textureBottomLeft);
 
// верхняя грань
cubeVertices[12] =
new VertexPositionNormalTexture(
topLeftFront, topNormal, textureBottomLeft);
cubeVertices[13] =
new VertexPositionNormalTexture(
topRightBack, topNormal, textureTopRight);
cubeVertices[14] =
new VertexPositionNormalTexture(
topLeftBack, topNormal, textureTopLeft);
cubeVertices[15] =
new VertexPositionNormalTexture(
topLeftFront, topNormal, textureBottomLeft);
cubeVertices[16] =
new VertexPositionNormalTexture(
topRightFront, topNormal, textureBottomRight);
cubeVertices[17] =
new VertexPositionNormalTexture(
topRightBack, topNormal, textureTopRight);
 
// нижняя грань
cubeVertices[18] =
new VertexPositionNormalTexture(
bottomLeftFront, bottomNormal, textureTopLeft);
cubeVertices[19] =
new VertexPositionNormalTexture(
bottomLeftBack, bottomNormal, textureBottomLeft);
cubeVertices[20] =
new VertexPositionNormalTexture(
bottomRightBack, bottomNormal, textureBottomRight);
cubeVertices[21] =
new VertexPositionNormalTexture(
bottomLeftFront, bottomNormal, textureTopLeft);
cubeVertices[22] =
new VertexPositionNormalTexture(
bottomRightBack, bottomNormal, textureBottomRight);
cubeVertices[23] =
new VertexPositionNormalTexture(
bottomRightFront, bottomNormal, textureTopRight);
 
// левая грань
cubeVertices[24] =
new VertexPositionNormalTexture(
topLeftFront, leftNormal, textureTopRight);
cubeVertices[25] =
new VertexPositionNormalTexture(
bottomLeftBack, leftNormal, textureBottomLeft);
cubeVertices[26] =
new VertexPositionNormalTexture(
bottomLeftFront, leftNormal, textureBottomRight);
cubeVertices[27] =
new VertexPositionNormalTexture(
topLeftBack, leftNormal, textureTopLeft);
cubeVertices[28] =
new VertexPositionNormalTexture(
bottomLeftBack, leftNormal, textureBottomLeft);
cubeVertices[29] =
new VertexPositionNormalTexture(
topLeftFront, leftNormal, textureTopRight);
 
// правая грань
cubeVertices[30] =
new VertexPositionNormalTexture(
topRightFront, rightNormal, textureTopLeft);
cubeVertices[31] =
new VertexPositionNormalTexture(
bottomRightFront, rightNormal, textureBottomLeft);
cubeVertices[32] =
new VertexPositionNormalTexture(
bottomRightBack, rightNormal, textureBottomRight);
cubeVertices[33] =
new VertexPositionNormalTexture(
topRightBack, rightNormal, textureTopRight);
cubeVertices[34] =
new VertexPositionNormalTexture(
topRightFront, rightNormal, textureTopLeft);
cubeVertices[35] =
new VertexPositionNormalTexture(
bottomRightBack, rightNormal, textureBottomRight);
 
// создание вершинного буфера
vertices = new Vertices<VertexPositionNormalTexture>(cubeVertices);
 
// создание индексного буфера
indices = new Indices<ushort>(new ushort[]{
0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35});
 
}
 
 
// Функция отрисовки
public void Draw(DrawState state)
{
// сохраняем текущее состояние визуализации в стэк
state.PushRenderState();
 
// отключаем кулинг и запись глубины
state.RenderState.DepthColourCull.CullMode = CullMode.None;
state.RenderState.DepthColourCull.DepthWriteEnabled = false;
 
// получаем шейдер
SkyBoxRendererEffect shader = null;
// Метод GetShader<> структуры DrawState получает экземпляр шейдера
// как говорит разработчик Xen: это самый быстрый способ работы с шейдерам в его библиотеке
shader = state.GetShader<SkyBoxRendererEffect>();
 
// устанавливаем текстуру шейдера
shader.SkyBoxCubeMap = texture;
 
// получаем матрицы камеры
Matrix proj, view;
Vector2 size = state.DrawTarget.Size;
Vector3 pos;
 
state.Camera.GetProjectionMatrix(out proj, ref size);
state.Camera.GetViewMatrix(out view);
state.Camera.GetCameraPosition(out pos);
 
// рассчитываем матрицу
shader.WVP = Matrix.CreateTranslation(pos) * view * proj;
 
// учтанавливаем шейдер текущим
// напомню, что все что мы далее будем рисовать на экране, будет
// использовать данный шейдер
shader.Bind(state);
 
// Рисуем скайбокс
vertices.Draw(state, indices, PrimitiveType.TriangleList);
 
// Восстанавливаем состояние визуализации из стэка
// заметте очень удобно ;)
state.PopRenderState();
}
 
#region Члены ICullable
 
// Рисуем скайбокс всегда
public bool CullTest(ICuller culler)
{
return true;
}
 
#endregion
}
}

 


Примечание:

Собственно всегда есть какие-то ограничения. XenFX так же имеет некоторые ограничения:
  • Нельзя использовать в качестве параметров шейдеров структуры. Разработчик не поддерживает такую возможность. Если Вам надо передавать в шейдер сложные данные, то используйте набор массивов.
  • XenFX не поддерживает многопроходные шейдеры, в следствие чего нет возможности для каждого шага указывать состояние устройства. Выходом из этого положения является ручная установка параметров устройства перед визуализацией и ручная визуализация нескольких проходов в виде отдельных техник.
  • Ну и бывают некоторые глюки при генерации кода, но у меня они встречались не часто.


Ну вот и все. Удачи!

2116 Прочтений •  [Blender + Xna + Xen = ??? (часть 2)] [08.08.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Blender + Xna + Xen = ??? (часть 2) Ukraine Vova 08.08.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка