logo
Работа с 3D-моделями в 3D max

5. Разработка модели освещения 3D сцены

Рассмотрим управление освещением и отображением теней в XNA. Существуют различные техники работы с тенями, мы рассмотрим отрисовку теней с использованием так называемого буфера трафаретов, или, по-английски - Stencil Buffer.

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

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

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

В этой программе так же разработаем перемещением источника света. В частности, мы применим для рисования объектов один направленный источник света, направление которого можно менять с помощью клавиш клавиатуры - координата Z изменяется с помощью клавиш-Z и X, координата X - С и V, координата Y - B и N. Изменение направления освещения влияет не только на освещение объектов, но и на тень.

6. Программная реализация системы моделирования движения 3D объекта modCls, который будет отвечать за хранение параметров, соответствующих этим объектам и за их визуализацию

public class modCls : Microsoft.Xna.Framework.DrawableGameComponent

{

//Модель

public Model myModel;

//Мировая матрица, матрицы вида и проекции

publicMatrix WorldMatrix;

publicMatrix ViewMatrix;

publicMatrix ProjectMatrix;

//Направление света

public Vector3 LightDirection;

//Матрица для отображения тени

Matrix shadow;

//Плоскость, на которой отображается тень

Plane sPlane;

//Соотношение сторон экрана

publicfloat aspectRatio;

//Для управления графическим устройством

GraphicsDeviceManager graphics;

//Конструктор получает на вход

//игровой класс, модель, объект для управления графическим устройством

public modCls(Game game, Model mod, GraphicsDeviceManager grf, Plane pl)

base(game)

{

myModel = mod;

graphics = grf;

sPlane = pl;

aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /

(float)graphics.GraphicsDevice.Viewport.Height;

LightDirection = new Vector3();

}

После того как мы получили базу которая хранит данные об объектам. Настраиваем вывод на экран этих моделей используя метод Draw()

public override void Draw(GameTime gameTime)

{

//Выводим объект

foreach (ModelMesh mesh in myModel.Meshes)

{

//Для каждого эффекта в сети

foreach (BasicEffecteffect in mesh.Effects)

{

//Включить источник направленного света №0

effect.DirectionalLight0.Enabled = true;

//Настроить параметры

effect.DirectionalLight0.DiffuseColor = Vector3.One;

effect.DirectionalLight0.SpecularColor = Vector3.One;

//Направление света - в класса Game1 мы меняем направление

//по клавиатурным командам

effect.DirectionalLight0.Direction = Vector3.Normalize(LightDirection);

effect.LightingEnabled = true;

//установить матрицы

effect.World = WorldMatrix;

effect.View = ViewMatrix;

effect.Projection = ProjectMatrix;

}

mesh.Draw();

}

//Создать матрицу тени

shadow = Matrix.CreateShadow(-LightDirection, sPlane);

graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;

graphics.GraphicsDevice.BlendState = BlendState.Opaque;

//Выводим тень

foreach (ModelMesh mesh in myModel.Meshes)

{

foreach (BasicEffecteffect in mesh.Effects)

{

effect.AmbientLightColor = Vector3.Zero;

effect.Alpha = 0.5f;

effect.DirectionalLight0.Enabled = false;

effect.DirectionalLight1.Enabled = false;

effect.DirectionalLight2.Enabled = false;

effect.View = ViewMatrix;

effect.Projection = ProjectMatrix;

//При выводе тени умножаем мировую матрицу

//на матрицу вывода тени

effect.World = WorldMatrix*shadow;

}

mesh.Draw();

}

base.Draw(gameTime);

}

После объявляем список переменных которые будет использоваться в проекте.

GraphicsDeviceManager graphics;

//Матрицы

Matrix viewMatrix;

Matrix projMatrix;

const int shadowMapWidthHeight = 2048;

//Модели

Model ball, cube, ball2;

// Позиция объекта, поворот

Vector3avatarPosition = new Vector3(0, -1f, -50);

float avatarlRotation;

//Массив моделей сцены

modCls[] cls;

//Игровой объект

modCls ballObj;

//Плоскость

modCls plane;

//Направление света

Vector3 LightDirection;

BoundingFrustum cameraFrustum = new BoundingFrustum(Matrix.Identity);

// Положение камеры

Vector3 cameraReference = new Vector3(0, 0, 10);

Vector3 thirdPersonReference = new Vector3(0, 200, -200);

// Скорости поворота и движения

float rotationSpeed = 1f / 60f;

float forwardSpeed = 50f / 60f;

//Поле зрения камеры

float viewAngle = MathHelper.ToRadians(45.0f);

//Расстояние от камеры до переднего и заднего плана

float nearClip = 1.0f;

float farClip = 2000.0f;

// Установка позиции камеры 2 - вид от третьего лица

//1 - от первого лица

int cameraState = 2;

//Соотношение сторон экрана

float aspectRatio;

Загружаем модели которые мы будем использовать в проекте. Создаем базу для хранения этих моделей.

protected override void LoadContent()

{

//Загрузка моделей

ball = Content.Load<Model>("ball");

cube = Content.Load<Model>("cube");

ball2 = Content.Load<Model>("ball2");

aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /

(float)graphics.GraphicsDevice.Viewport.Height;

cls = new modCls[75];

LightDirection = new Vector3(80, -136, 80);

}

Метод отвечающий за изменения положения источника света. За изменения по оси Z отвечают клавиши Z и X(первая уменьшает позицию, вторая увеличивает), ось X- C и V, ось Y- B и N.

//Изменение позиции источника света

void LightSourceControl()

{

//Получим состояние клавиатуры

KeyboardState key = Keyboard.GetState();

//Клавиша z - уменьшим позицию по Z

if (key.IsKeyDown(Keys.Z))

{

LightDirection.Z -= 0.5f;

}

//Клавиша x - увеличим Z

if (key.IsKeyDown(Keys.X))

{

LightDirection.Z += 0.5f;

}

//c - уменьшим X

if (key.IsKeyDown(Keys.C))

{

LightDirection.X -= 0.5f;

}

//"v" - увеличим X

if (key.IsKeyDown(Keys.V))

{

LightDirection.X += 0.5f;

}

//"b" - увеличим Y

if (key.IsKeyDown(Keys.B))

{

LightDirection.Y += 0.5f;

}

//"n" - уменьшим Y

if (key.IsKeyDown(Keys.N))

{

LightDirection.Y -= 0.5f;

}

//Выведем в заголовок окна информацию о направлении

this.Window.Title = "Light source: " + LightDirection.ToString();

}

За движения объекта отвечает класс UpdateAvatarPosition(). Управление производится стрелками клавиатуры. При нажатие на клавишу вперед или назад производится соответствующие движения. А при нажатие клавиш влево или вправо происходит соответствующий поворот относительно оси Y.

//Обновляем состояние объекта

void UpdateAvatarPosition()

{

KeyboardState keyboardState = Keyboard.GetState();

//Поворот влево

if (keyboardState.IsKeyDown(Keys.Left))

{

avatarlRotation += rotationSpeed;

}

//Поворот вправо

if (keyboardState.IsKeyDown(Keys.Right))

{

avatarlRotation -= rotationSpeed;

}

//Движение вперед

if (keyboardState.IsKeyDown(Keys.Up))

{

Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);

Vector3 v = new Vector3(0, 0, forwardSpeed);

v = Vector3.Transform(v, forwardMovement);

avatarPosition.Z += v.Z;

avatarPosition.X += v.X;

while (IsCollide())

{

avatarPosition.Z -= v.Z;

avatarPosition.X -= v.X;

}

}

//Движение назад

if (keyboardState.IsKeyDown(Keys.Down))

{

Matrix forwardMovement = Matrix.CreateRotationY(avatarlRotation);

Vector3 v = new Vector3(0, 0, -forwardSpeed);

v = Vector3.Transform(v, forwardMovement);

avatarPosition.Z += v.Z;

avatarPosition.X += v.X;

while (IsCollide())

{

avatarPosition.Z -= v.Z;

avatarPosition.X -= v.X;

}

}

//Уменьшение угла обзора камеры

if (keyboardState.IsKeyDown(Keys.R))

{

viewAngle -= MathHelper.ToRadians(1.0f);

}

//Увеличение угла обзора камеры

if (keyboardState.IsKeyDown(Keys.F))

{

viewAngle += MathHelper.ToRadians(1.0f);

}

//Если новый угол обзора вышел за дозволенные пределы

//изменяем его

if (viewAngle > MathHelper.ToRadians(180.0f)) viewAngle = MathHelper.ToRadians(179.9f);

if (viewAngle < MathHelper.ToRadians(0.9f)) viewAngle = MathHelper.ToRadians(1f);

// Выход из программы при нажатие Esc.

if (keyboardState.IsKeyDown(Keys.Escape))

{

Exit();

}

}

Создаем булевую функцию отвечающие за проверку столкновений объектов.

bool IsCollide()

{

//Для объекта BoundingSphere, соответствующего

//текущему объекту сцены

BoundingSphere b1;

//Получить BoundingSphere для игрового объекта

BoundingSphere b = ball.Meshes[0].BoundingSphere;

//Установить центр сферы в соответствии с положением

//игрового объекта

b.Center =avatarPosition;

//Переменная для хранения вектора размера модели

Vector3 scale;

//Переменная для хранения информации о повороте модели

Quaternion rotation;

//Переменая для хранения информации о позиции модели

Vector3 translation;

//Цикл обхода объектов сцены

for (int i = 0; i < 75; i++)

{

//Получить BoundingBox для текущего объекта

b1 =cls[i].myModel.Meshes[0].BoundingSphere;

//Получить параметры - размер, поворот, позицию для объекта

cls[i].WorldMatrix.Decompose(out scale, out rotation, out translation);

//Установить соответствии с позицией объекта

b1.Center = translation;

//Если сферы игрового объекта и текущего объекта

if (b1.Intersects(b))

{

//Возвратим True

return true;

}

} //Если выполняется этот код -

//столкновения не было

//и вернем false

return false;

}

Последний метод отвечающий за построение самой сцены и самого объекта

//Вывод объектов сцены

void DrawScene()

{

Components.Clear();

int i = 0;

//Она немного выше плоскости, которую мы выводим на экран

//для того, чтобы тень была видна

Plane pl1 = new Plane(new Vector3(0, -2f, 0), new Vector3(2, -2f, 1), new Vector3(-1, -2f, -2));

//Вывести кубы, расположенные в пять рядов

//по пять штук в трех уровнях

for (int x = 0; x < 5; x++)

{

for (int y = 0; y < 3; y++)

{

for (int z = 0; z < 5; z++)

{

//Добавляем в массив новый объект

cls[i] = new modCls(this, ball2, graphics, pl1);

//Устанавливаем его свойства

cls[i].WorldMatrix = Matrix.CreateTranslation(x * 40, y*3, z * 40);

cls[i].ViewMatrix = viewMatrix;

cls[i].ProjectMatrix = projMatrix;

cls[i].LightDirection = LightDirection;

//Добавляем в коллекцию компонентов

Components .Add (cls[i]);

i++;

}

}

}

ballObj = new modCls(this, ball, graphics, pl1);

ballObj.WorldMatrix = Matrix.CreateRotationY(avatarlRotation) * Matrix.CreateTranslation(avatarPosition);

ballObj.ViewMatrix = viewMatrix;

ballObj.ProjectMatrix = projMatrix;

ballObj.LightDirection = LightDirection;

Components.Add(ballObj);

plane = new modCls(this, Content.Load<Model>("plane"), graphics, new Plane());

//Настраиваем параметры плоскости

plane.WorldMatrix = Matrix.CreateScale(100) * Matrix.CreateRotationY(MathHelper.ToRadians(90)) *

Matrix.CreateRotationZ(MathHelper.ToRadians(90)) *

Matrix.CreateTranslation(80, -2.5f, 80);

plane.ViewMatrix = viewMatrix;

plane.ProjectMatrix = projMatrix;

plane.LightDirection = LightDirection;

Components.Add(plane);

}

}

}