Навигация
· XNA FAQ
· С чего начать
· Конкурсы
· Обратная связь
· XNA Блоги
Сейчас на сайте
· Гостей: 1

· Пользователей: 0

· Всего пользователей: 3,684
· Новый пользователь: headron
Последние фото
Эх, чуть не проспал закрытие.
Эх, чуть не проспал ...
Альбом: XNA Engine

GB
GB
Альбом: XNA Engine

South Park Coon & Friends
South Park Coon & Fr...
Альбом: XNA Games

Блоги
yavshoke
» XboxOne - интерес...
dampirik
» Push уведомления ...
dampirik
» Реклама,статистик...
Chort
» XNA и StartCoroutine
Chort
» Curve Class
dampirik
» Реклама, статисти...
dampirik
» Увеличение скорос...
dampirik
» Реклама, статисти...
general
» Распаковка DxtCom...
general
» Как работать с XN...
Поддержка
microsoft.com
1gb.ru - Дом для вашего сайта
Статистика посещений:

Осуществление охраны банка начинается с тщательного изучения объекта.
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;
}
}
}

 


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

Пример создания такой структуры для использования без Xen, т.е. непосредственно в XNA можно посмотреть в блоге Дмитрия Тимофеева (General) в статье, под названием «Terrain Geomorphing in the Vertex Shader»


Следующим классом, реализацией которого мы займемся, будет 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)
{
// вычисление положения на кубической кривой Безье
// описание смотрите тут: http://ru.wikipedia.org/wiki/Кривая_Безье
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[i];
// 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 не поддерживает многопроходные шейдеры, в следствие чего нет возможности для каждого шага указывать состояние устройства. Выходом из этого положения является ручная установка параметров устройства перед визуализацией и ручная визуализация нескольких проходов в виде отдельных техник.
  • Ну и бывают некоторые глюки при генерации кода, но у меня они встречались не часто.


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


Автор:PAX
Комментарии
#1 | pax 26.10.2009 23:18:55
Спасибо mike за проделанную работу по переводу статьи на движок сайта!
Сходу заметил ошибку у себя
написано rfvths а надо камеры
#2 | mike 26.10.2009 23:22:56
исправил
#3 | pax 26.10.2009 23:26:24
А может статью надо перенести в категорию "Конкурс" из категории "Курсы"?
#4 | pax 27.10.2009 07:17:35
А можно отделить бонусную часть в отдельную статью с названием "Использование шейдеров в Xen"? А то как-то она незаметна, когда читаешь...
#5 | SolarWind 27.10.2009 09:48:53
2PAX мегареспектище за статью, сам с недельку копаюсь с XEN (заметь это на 7 дней раньше появления статьи), чет приглянулся он мне. Как API (как правильно было замечено в статье - это не движок) XEN просто великолепен, понятна его концепция, единственное, что я сделал с самого начала, это переворошил исходники на предмет организации исходных файлов и папок (знаю, что если выйдет обновление, то я просто устану повторять это у себя, но мне так удобней работать с ним).
Еще один недостаток XEN - отсутствие документации, но опять-же, как было замечано в статье, есть 25 прекрасно откоментированных примера, так что совет, всем кто желает покапаться с XEN, не пожалейте времени и прочитайте и разберите все туторы движка (лично я потратил на это около двух дней). А еще лучше, для полного въезда в тему напишите сами приложение на основе туторов (я собирал воедино пример с NormalMapping и тенями и грузил свою текстуренную модель + динамический источник света).
А за статью мегаспасибо, у меня теперь есть ясный вижен как реализовывать граф сцены, он конечно и до этого был, но теперь точно все окончательно прояснилось.
#6 | pax 27.10.2009 10:01:04
Рад что понравилось Smile
#7 | pax 30.10.2009 18:45:19
Вот видео к статье:
YouTube Video
#8 | pumpurumer 04.11.2009 02:09:37
по организации кода хочу совет автору дать, мб оно уже реализовано.
написать xsd схему документа xml-ного, который генерируется на выходе плагина, затем по этой схеме сгенерировать python классы, которые будут потом сериализовыватся в xml, аналогичным образом сделать для c#, затем копипастом сделать классы без xml атрибутов (c#) и использовать binaryserialization.
мб один раз немного больше времени затратите на написание такой обертки, но потом не будите иметь гимарой при изменении схемы выгрузки/загрузки.
те при такой стратегии, при необходимости добовления новых свойств в формат обменна данных, вы изменяете xsd схему, запускаете скрипт который по xsd схеме соотвецтвуюшими программами генерирует код на c# и python. а потом вы просто вносите изменения в плагин для выгрузки, программу конвертации в binary формат, в загружалку моделей.

В целом статья хорошая, спасибо автору.
PS: возможно вышеописанное сам закодирую.
PS2: в статье ненашол ссылки на исходники, мб я слепой. пришлой по сайту искать.

Ссылки по теме:
[u name=xsd2python]http://www.rexx.com/~dkuhlman/generateDS.html[/u]
[u name=xsd2c#]http://msdn.microsoft.com/ru-ru/library/x6c1kb0s(en-us,VS.80).aspx[/u]
http://msdn.microsoft.com/ru-ru/library/[u]system.runtime.serialization.formatters.binary.binaryformatter.aspx[u]
#9 | pax 05.11.2009 09:15:19
Исходники в файлах портала, а ссылка есть в начале первой части (http://www.xnadev...page_id=50). Про XSD не думал, так как хотелось сделать проще, да и не пользовался до этого XSD. На самом деле лучше сразу создавать бинарный формат, иначе скорость загрузки XML очень низкая и требует больше ресурсов памяти. Пример в статье должен был отобразить всю начинку так, чтобы всем было просто разобраться.
#10 | zintus 01.12.2009 19:14:19
Спасибо большое =)
Впервые читаю внятное именно про подготовку контента, а не конкретные технологии/реализации.
Добавить комментарий
Пожалуйста, залогиньтесь для добавления комментария.
Рейтинги
Рейтинг доступен только для пользователей.

Пожалуйста, залогиньтесь или зарегистрируйтесь для голосования.

Отлично! Отлично! 100% [4 Голоса]
Очень хорошо Очень хорошо 0% [Нет голосов]
Хорошо Хорошо 0% [Нет голосов]
Удовлетворительно Удовлетворительно 0% [Нет голосов]
Плохо Плохо 0% [Нет голосов]
Авторизация
Логин

Пароль



Вы не зарегистрированы?
Нажмите здесь для регистрации.

Забыли пароль?
Запросите новый здесь.
Мини-чат
Вы должны авторизироваться, чтобы добавить сообщение.

27.08.2014
Я умею немного на asp.net + html и css

22.08.2014
на ASP mvc 3 есть пару проектов. Могу помочь, если нужно. Обидно, если закроется Frown

21.08.2014
я тоже ноль

21.08.2014
Я в вебе только с php занимался да и то на уровне чтоб работало.

21.08.2014
Я в вебе полный ноль…

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

21.08.2014
не знаю всех нюансов по оплате и все хорошее когда нибудь заканчивается

21.08.2014
А что случилось?

21.08.2014
похоже сайт будет работать до 28го числа

09.08.2014
Апи пока не видел. Но есть приложение в магазине Live Lock Screen BETA, так что думаю скоро будет

08.08.2014
Я про API для Update1. На нем работает это

08.08.2014
А что именно нужно? Чтото и сейчас открыто http://msdn.micro.
...105).aspx

06.08.2014
Кто-нибудь слышал об открытии доступа к Lock Screen Api?

31.07.2014
VPDExpress на базе MVS 2012, ни в какую не ловит исключения. Даже если их сам создаешь. И всех так?

25.07.2014
С днем системного администратора причастных к этой профессии! По случаю - тортик от жены

RSS каналы сайта
XNA - Новости
XNA - Статьи
XNA - Форум
XNA - Галерея
XNA - Файлы
Время загрузки: 0,08 секунд 8,709,460 уникальных посетителей