Источник света

Источник света

В рамках нескольких следующих статей я хочу расмотреть реализацию различных стандартных моделей освещения с использованием XNA Framework.

Освещенность объекта в общем случае зависит от различных параметров, однако основным является позиция источника света (на самом деле, направление на источник света, но сейчас это не так важно).

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


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

Приступим. Создадим новый класс с именем Light, в нем зададим пока только переменные, отвечающие за положение источника света.

Position – текущая позиция источника света.

DefaultPosition – начальное положение источника света. Эта переменная может позже пригодиться для экпериментов.

class
Light : DrawableGameComponent

{


public
Vector3 Position;


public
Vector3 DefaultPosition = new
Vector3(0, 0, 2);


public Light(Game game) : base(game)

{

Position = DefaultPosition;

}

}

Теперь добавим управление, для этого переопределеним метод Update.

Наш источник света будет просто вращаться вокруг начала координат, при этом врегда будет находиться на расстоянии равном длинне вектора DefaultPosition.

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

Добавим также переменную отвечающую за скорость перемещения источника света.


Vector3 angle;


float speed = 40;


public
override
void Update(GameTime gameTime)

{


float time = (float)gameTime.ElapsedGameTime.TotalSeconds;


KeyboardState ks = Keyboard.GetState();


float seconds = (float)(gameTime.ElapsedGameTime.TotalSeconds);


float dx = 0;


float dy = 0;


if (ks.IsKeyDown(Keys.D))

{

dx = speed;

}


if (ks.IsKeyDown(Keys.A))

{

dx = -speed;

}


if (ks.IsKeyDown(Keys.S))

{

dy = speed;

}


if (ks.IsKeyDown(Keys.W))

{

dy = -speed;

}


float yaw = MathHelper.ToRadians(dx * seconds);


float pitch = MathHelper.ToRadians(dy * seconds);

angle = new
Vector3(angle.X + pitch, angle.Y + yaw, angle.Z);

Position = (Vector3.Transform(DefaultPosition,


Matrix.CreateRotationZ(angle.Z) *


Matrix.CreateRotationY(angle.Y) *


Matrix.CreateRotationX(angle.X)));


base.Update(gameTime);

}

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

Для того, чтобы нарисовать сферу я воспользуюсь библиотекой примитивов (http://creators.xna.com/en-US/sample/primitives3D). Нужно подключить ее к проекту References->Add Reference и в окне Browse выбрать соответствующую сборку.

После этого класс будет выглядеть следующим образом:


class
Light : DrawableGameComponent

{


public
Vector3 Position;


SpherePrimitive sphere;


Vector3 angle;


float speed = 40;


public
Vector3 DefaultPosition = new
Vector3(0, 0, 2);


public Light(Game game) : base(game)

{

sphere = new
SpherePrimitive(game.GraphicsDevice, 0.1f, 10);

Position = DefaultPosition;

}


public
override
void Update(GameTime gameTime)

{


float time = (float)gameTime.ElapsedGameTime.TotalSeconds;


KeyboardState ks = Keyboard.GetState();


float seconds = (float)(gameTime.ElapsedGameTime.TotalSeconds);


float dx = 0;


float dy = 0;


if (ks.IsKeyDown(Keys.D))

{

dx = speed;

}


if (ks.IsKeyDown(Keys.A))

{

dx = -speed;

}


if (ks.IsKeyDown(Keys.S))

{

dy = speed;

}


if (ks.IsKeyDown(Keys.W))

{

dy = -speed;

}


float yaw = MathHelper.ToRadians(dx * seconds);


float pitch = MathHelper.ToRadians(dy * seconds);

angle = new
Vector3(angle.X + pitch, angle.Y + yaw, angle.Z);

Position = (Vector3.Transform(DefaultPosition,


Matrix.CreateRotationZ(angle.Z) *


Matrix.CreateRotationY(angle.Y) *


Matrix.CreateRotationX(angle.X)));


base.Update(gameTime);

}


public
void Draw(Matrix view, Matrix projection)

{


Matrix world = Matrix.CreateTranslation(Position);

sphere.Draw(world, view, projection, Color.White);

}

}

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

Из-за этого придется самостоятельно вызывать метод Draw из Game1, в тех случаях когда нужно рисовать источник света.

Теперь перейдем в Game1 и добавим все необходимое для использование источника света (приведен только измененный код).


public
class
Game1 : Microsoft.Xna.Framework.Game

{


Light light;

//…


protected
override
void Initialize()

{

//…

light = new
Light(this);

Components.Add(light);


base.Initialize();

}


protected
override
void Draw(GameTime gameTime)

{

//…

GraphicsDevice.Clear(Color.CornflowerBlue);


Matrix view = Matrix.CreateLookAt(new
Vector3(0,0,3), Vector3.Zero, Vector3.Up);


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);

light.Draw(view, proj);


base.Draw(gameTime);

}

}

Теперь для проверки работоспособности того, что мы только что написали добавим в сцену модель чайника и осветим ее при помощи BasicEffect.


BasicEffect basicEffect;


TeapotPrimitive teapot;


protected
override
void Initialize()

{


// TODO: Add your initialization logic here

Components.Add(new
FPSCounter(this));

light = new
Light(this);

Components.Add(light);

basicEffect = new
BasicEffect(GraphicsDevice, null);

teapot = new
TeapotPrimitive(GraphicsDevice);


base.Initialize();

}


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


Matrix view = Matrix.CreateLookAt(new
Vector3(0,0,3), Vector3.Zero, Vector3.Up);


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);


// TODO: Add your drawing code here


basicEffect.World = Matrix.Identity;

basicEffect.View = view;

basicEffect.Projection = proj;

basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;

basicEffect.DirectionalLight0.DiffuseColor = new
Vector3(1, 1, 1);

basicEffect.LightingEnabled = true;

basicEffect.DirectionalLight0.Enabled = true;

teapot.Draw(basicEffect);

light.Draw(view, proj);


base.Draw(gameTime);

}

Стоит обратить внимание на строчку

basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;

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

Исходный код:

Light.cs

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Input;

using Primitives3D;

using Microsoft.Xna.Framework.Graphics;

namespace MyGame

{


class
Light : DrawableGameComponent

{


public
Vector3 Position;


SpherePrimitive sphere;


Vector3 angle;


float speed = 40;


public
Vector3 DefaultPosition = new
Vector3(0, 0, 2);


public Light(Game game) : base(game)

{

sphere = new
SpherePrimitive(game.GraphicsDevice, 0.1f, 10);

Position = DefaultPosition;

}


public
override
void Update(GameTime gameTime)

{


float time = (float)gameTime.ElapsedGameTime.TotalSeconds;


KeyboardState ks = Keyboard.GetState();


float seconds = (float)(gameTime.ElapsedGameTime.TotalSeconds);


float dx = 0;


float dy = 0;


if (ks.IsKeyDown(Keys.D))

{

dx = speed;

}


if (ks.IsKeyDown(Keys.A))

{

dx = -speed;

}


if (ks.IsKeyDown(Keys.S))

{

dy = speed;

}


if (ks.IsKeyDown(Keys.W))

{

dy = -speed;

}


float yaw = MathHelper.ToRadians(dx * seconds);


float pitch = MathHelper.ToRadians(dy * seconds);

angle = new
Vector3(angle.X + pitch, angle.Y + yaw, angle.Z);

Position = (Vector3.Transform(DefaultPosition,


Matrix.CreateRotationZ(angle.Z) *


Matrix.CreateRotationY(angle.Y) *


Matrix.CreateRotationX(angle.X)));


base.Update(gameTime);

}


public
void Draw(Matrix view, Matrix projection)

{


Matrix world = Matrix.CreateTranslation(Position);

sphere.Draw(world, view, projection, Color.White);

}

}

}

Game1.cs

using System;

using System.Collections.Generic;

using System.Linq;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Audio;

using Microsoft.Xna.Framework.Content;

using Microsoft.Xna.Framework.GamerServices;

using Microsoft.Xna.Framework.Graphics;

using Microsoft.Xna.Framework.Input;

using Microsoft.Xna.Framework.Media;

using Microsoft.Xna.Framework.Net;

using Microsoft.Xna.Framework.Storage;

using Primitives3D;

namespace MyGame

{


///
<summary>


/// This is the main type for your game


///
</summary>


public
class
Game1 : Microsoft.Xna.Framework.Game

{


GraphicsDeviceManager graphics;


SpriteBatch spriteBatch;


Light light;


BasicEffect basicEffect;


TeapotPrimitive teapot;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

graphics.SynchronizeWithVerticalRetrace = false;

IsFixedTimeStep = false;

}


///
<summary>


/// Allows the game to perform any initialization it needs to before starting to run.


/// This is where it can query for any required services and load any non-graphic


/// related content. Calling base.Initialize will enumerate through any components


/// and initialize them as well.


///
</summary>


protected
override
void Initialize()

{


// TODO: Add your initialization logic here

Components.Add(new
FPSCounter(this));

light = new
Light(this);

Components.Add(light);

basicEffect = new
BasicEffect(GraphicsDevice, null);

teapot = new
TeapotPrimitive(GraphicsDevice);


base.Initialize();

}


///
<summary>


/// LoadContent will be called once per game and is the place to load


/// all of your content.


///
</summary>


protected
override
void LoadContent()

{


// Create a new SpriteBatch, which can be used to draw textures.

spriteBatch = new
SpriteBatch(GraphicsDevice);


// TODO: use this.Content to load your game content here

}


///
<summary>


/// UnloadContent will be called once per game and is the place to unload


/// all content.


///
</summary>


protected
override
void UnloadContent()

{


// TODO: Unload any non ContentManager content here

}


///
<summary>


/// Allows the game to run logic such as updating the world,


/// checking for collisions, gathering input, and playing audio.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Update(GameTime gameTime)

{


// Allows the game to exit


if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)


this.Exit();


// TODO: Add your update logic here


base.Update(gameTime);

}


///
<summary>


/// This is called when the game should draw itself.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.CornflowerBlue);


Matrix view = Matrix.CreateLookAt(new
Vector3(0,0,3), Vector3.Zero, Vector3.Up);


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);


// TODO: Add your drawing code here

basicEffect.World = Matrix.Identity;

basicEffect.View = view;

basicEffect.Projection = proj;

basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;

basicEffect.DirectionalLight0.DiffuseColor = new
Vector3(1, 1, 1);

basicEffect.LightingEnabled = true;

basicEffect.DirectionalLight0.Enabled = true;

teapot.Draw(basicEffect);

light.Draw(view, proj);


base.Draw(gameTime);

}

}

}

Реклама
Запись опубликована в рубрике Uncategorized с метками , , , , . Добавьте в закладки постоянную ссылку.

2 комментария на «Источник света»

  1. Игорь:

    Огромное спасибо! Статья очень сильно помогла!

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s