Также нам понадобится простой шейдер файла эффекта (Effect), который мы будем использовать для рендеринга skybox’а.
Я отключил освещение и Z-буффер, а также включил texture clamping, чтобы увеличить производительность и предотвратить показ краев текстур.
float4x4 worldViewProjection;
texture baseTexture;
sampler baseSampler =
sampler_state
{
Texture = < baseTexture >;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
ADDRESSU = CLAMP;
ADDRESSV = CLAMP;
};
struct VS_INPUT
{
float4 ObjectPos: POSITION;
float2 TextureCoords: TEXCOORD0;
};
struct VS_OUTPUT
{
float4 ScreenPos: POSITION;
float2 TextureCoords: TEXCOORD0;
};
struct PS_OUTPUT
{
float4 Color: COLOR;
};
VS_OUTPUT SimpleVS(VS_INPUT In)
{
VS_OUTPUT Out;
Out.ScreenPos = mul(In.ObjectPos, worldViewProjection);
Out.TextureCoords = In.TextureCoords;
return Out;
}
PS_OUTPUT SimplePS(VS_OUTPUT In)
{
PS_OUTPUT Out;
Out.Color = tex2D(baseSampler,In.TextureCoords);
return Out;
}
technique Simple
{
pass Single_Pass
{
LIGHTING = FALSE;
ZENABLE = FALSE;
ZWRITEENABLE = FALSE;
ALPHATESTENABLE = FALSE;
ALPHABLENDENABLE = FALSE;
CULLMODE = CCW;
VertexShader = compile vs_1_1 SimpleVS();
PixelShader = compile ps_1_1 SimplePS();
}
}
Поскольку теперь у нас есть простой шейдер для рендеринга, давайте спроектируем класс skybox’а.
Мы будем использовать модель интерфейса GameComponent.
#region using’и
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace SkyBox
{
class SkyBox : GameComponent,IDrawable
{
Я добавил IDrawable к компоненту skybox’а. Это добавило некоторые функции под разделом со следующим названием:
Вы можете автоматически сгенерировать описания этого члена, если кликнете правой кнопкой мыши на слове IDrawable и выберете Implement Interface. Это меню доступно, только если вы щелкаете на IDrawable первый раз. Если вы забыли выбрать Implement Interface, то вы можете вернуться и удалить слово IDrawable, а потом напечатать его снова, кликнув затем по нему правой кнопкой мыши и выбрав Implement Interface.
Skybox будет содержать шесть сторон, поэтому нам нужно место для шести текстур:
Texture2D[] textures = new Texture2D[6];
Effect effect;
Давайте объявим буфер вершин, буфер индексов и объявление буфера (vertex declaration), чтобы мы могли рендерить skybox вручную. Этот подход лучше, чем использование сетки (mesh), так как вы можете рендерить только видимые камере стороны skybox’а и пропустить остальные стороны.
VertexBuffer vertices;
IndexBuffer indices;
VertexDeclaration vertexDecl;
Запомним направление и позицию камеры, чтобы skybox в последствии мог быть преобразован правильно:
Vector3 vCameraDirection;
Vector3 vCameraPosition;
Файлу эффекта понадобится комбинированная мировая/видовая/проекционная (World * View * Projection) матрица преобразования:
Matrix viewMatrix;
Matrix projectionMatrix;
Matrix worldMatrix;
Давайте сохраним указатель на content manager. Это, вероятно, можно оптимизировать.
Базовый класс игрового компонента требует от нас, чтобы мы передали указатель на экземпляр класса Game:
public SkyBox(Game g)
: base(g)
{
}
Давайте определим методы-аксессоры для переменных члена:
public Vector3 CameraDirection
{
get { return vCameraDirection; }
set { vCameraDirection = value; }
}
Когда изменяется позиция камеры, нам необходимо пересчитать мировую матрицу преобразования:
public Vector3 CameraPosition
{
get { return vCameraPosition; }
set
{
vCameraPosition = value;
worldMatrix = Matrix.CreateTranslation(vCameraPosition);
}
}
public Matrix ViewMatrix
{
set { viewMatrix = value; }
}
public Matrix ProjectionMatrix
{
set { projectionMatrix = value; }
}
public ContentManager ContentManager
{
set { content = value; }
}
Внутри метода инициализации нам надо сделать работу по созданию skybox’а:
public override void Initialize()
{
base.Initialize();
Загружаем все шесть сторон (текстур) skybox'а:
textures[0] = content.Load<Texture2D>("Skybox\\back");
textures[1] = content.Load<Texture2D>("Skybox\\front");
textures[2] = content.Load<Texture2D>("Skybox\\bottom");
textures[3] = content.Load<Texture2D>("Skybox\\top");
textures[4] = content.Load<Texture2D>("Skybox\\left");
textures[5] = content.Load<Texture2D>("Skybox\\right");
effect = content.Load<Effect>("Skybox\\skybox");
Получаем указатель на графическое устройство, чтобы мы могли создать вершинный и индексный буферы:
IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)
Game.Services.GetService(typeof(IGraphicsDeviceService));
Для рендеринга от нас потребуется определить объявление вершин (vertex declaration). Поскольку мы используем тип вершины PositionTexture, то он содержит два элемента: Position (Vector3) и TextureCoordinate (Vector2):
vertexDecl = new VertexDeclaration(graphicsService.GraphicsDevice,
new VertexElement[] {
new VertexElement(0,0,VertexElementFormat.Vector3,
VertexElementMethod.Default,
VertexElementUsage.Position,0),
new VertexElement(0,sizeof(float)*3,VertexElementFormat.Vector2,
VertexElementMethod.Default,
VertexElementUsage.TextureCoordinate,0)});
Создаем вершинный буфер с 4*6 вершинами. Он будет содержать по четыре вершины на каждую сторону skybox’а. Установите ResourceUsage на WriteOnly, чтобы оптимизировать производительность:
vertices = new VertexBuffer(graphicsService.GraphicsDevice,
typeof(VertexPositionTexture),
4 * 6,
ResourceUsage.WriteOnly);
Заполните вершинный буфер:
VertexPositionTexture[] data = new VertexPositionTexture[4*6];
Vector3 vExtents = new Vector3(500, 500, 500);
data[0].Position = new Vector3(vExtents.X, -vExtents.Y, -vExtents.Z);
data[0].TextureCoordinate.X = 1.0f; data[0].TextureCoordinate.Y = 1.0f;
data[1].Position = new Vector3(vExtents.X, vExtents.Y, -vExtents.Z);
data[1].TextureCoordinate.X = 1.0f; data[1].TextureCoordinate.Y = 0.0f;
data[2].Position = new Vector3(-vExtents.X, vExtents.Y, -vExtents.Z);
data[2].TextureCoordinate.X = 0.0f; data[2].TextureCoordinate.Y = 0.0f;
data[3].Position = new Vector3(-vExtents.X, -vExtents.Y, -vExtents.Z);
data[3].TextureCoordinate.X = 0.0f; data[3].TextureCoordinate.Y = 1.0f;
data[4].Position = new Vector3(-vExtents.X, -vExtents.Y, vExtents.Z);
data[4].TextureCoordinate.X = 1.0f; data[4].TextureCoordinate.Y = 1.0f;
data[5].Position = new Vector3(-vExtents.X, vExtents.Y, vExtents.Z);
data[5].TextureCoordinate.X = 1.0f; data[5].TextureCoordinate.Y = 0.0f;
data[6].Position = new Vector3(vExtents.X, vExtents.Y, vExtents.Z);
data[6].TextureCoordinate.X = 0.0f; data[6].TextureCoordinate.Y = 0.0f;
data[7].Position = new Vector3(vExtents.X, -vExtents.Y, vExtents.Z);
data[7].TextureCoordinate.X = 0.0f; data[7].TextureCoordinate.Y = 1.0f;
data[8].Position = new Vector3(-vExtents.X, -vExtents.Y, -vExtents.Z);
data[8].TextureCoordinate.X = 1.0f; data[8].TextureCoordinate.Y = 0.0f;
data[9].Position = new Vector3(-vExtents.X, -vExtents.Y, vExtents.Z);
data[9].TextureCoordinate.X = 1.0f; data[9].TextureCoordinate.Y = 1.0f;
data[10].Position = new Vector3(vExtents.X, -vExtents.Y, vExtents.Z);
data[10].TextureCoordinate.X = 0.0f; data[10].TextureCoordinate.Y = 1.0f;
data[11].Position = new Vector3(vExtents.X, -vExtents.Y, -vExtents.Z);
data[11].TextureCoordinate.X = 0.0f; data[11].TextureCoordinate.Y = 0.0f;
data[12].Position = new Vector3(vExtents.X, vExtents.Y, -vExtents.Z);
data[12].TextureCoordinate.X = 0.0f; data[12].TextureCoordinate.Y = 0.0f;
data[13].Position = new Vector3(vExtents.X, vExtents.Y, vExtents.Z);
data[13].TextureCoordinate.X = 0.0f; data[13].TextureCoordinate.Y = 1.0f;
data[14].Position = new Vector3(-vExtents.X, vExtents.Y, vExtents.Z);
data[14].TextureCoordinate.X = 1.0f; data[14].TextureCoordinate.Y = 1.0f;
data[15].Position = new Vector3(-vExtents.X, vExtents.Y, -vExtents.Z);
data[15].TextureCoordinate.X = 1.0f; data[15].TextureCoordinate.Y = 0.0f;
data[16].Position = new Vector3(-vExtents.X, vExtents.Y, -vExtents.Z);
data[16].TextureCoordinate.X = 1.0f; data[16].TextureCoordinate.Y = 0.0f;
data[17].Position = new Vector3(-vExtents.X, vExtents.Y, vExtents.Z);
data[17].TextureCoordinate.X = 0.0f; data[17].TextureCoordinate.Y = 0.0f;
data[18].Position = new Vector3(-vExtents.X, -vExtents.Y, vExtents.Z);
data[18].TextureCoordinate.X = 0.0f; data[18].TextureCoordinate.Y = 1.0f;
data[19].Position = new Vector3(-vExtents.X, -vExtents.Y, -vExtents.Z);
data[19].TextureCoordinate.X = 1.0f; data[19].TextureCoordinate.Y = 1.0f;
data[20].Position = new Vector3(vExtents.X, -vExtents.Y, -vExtents.Z);
data[20].TextureCoordinate.X = 0.0f; data[20].TextureCoordinate.Y = 1.0f;
data[21].Position = new Vector3(vExtents.X, -vExtents.Y, vExtents.Z);
data[21].TextureCoordinate.X = 1.0f; data[21].TextureCoordinate.Y = 1.0f;
data[22].Position = new Vector3(vExtents.X, vExtents.Y, vExtents.Z);
data[22].TextureCoordinate.X = 1.0f; data[22].TextureCoordinate.Y = 0.0f;
data[23].Position = new Vector3(vExtents.X, vExtents.Y, -vExtents.Z);
data[23].TextureCoordinate.X = 0.0f; data[23].TextureCoordinate.Y = 0.0f;
vertices.SetData<VertexPositionTexture>(data);
Теперь нам надо создать индексный буфер, который будет использоваться для индексирования каждой поверхности во время рендеринга:
indices = new IndexBuffer(graphicsService.GraphicsDevice,
typeof(short),6*6,
ResourceUsage.WriteOnly);
short[] ib = new short[6 * 6];
for (int x = 0; x < 6; x++)
{
ib[x * 6 + 0] = (short) (x * 4 + 0);
ib[x * 6 + 2] = (short) (x * 4 + 1);
ib[x * 6 + 1] = (short) (x * 4 + 2);
ib[x * 6 + 3] = (short) (x * 4 + 2);
ib[x * 6 + 5] = (short) (x * 4 + 3);
ib[x * 6 + 4] = (short) (x * 4 + 0);
}
indices.SetData<short>(ib);
}
Любой код, который должен быть выполнен между каждым кадром, помещаем в метод Update:
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
}
Вот реализация интерфейса IDrawable:
#region Члены IDrawable
public void Draw(GameTime gameTime)
{
if (vertices == null)
return;
Давайте начнем использовать эффект (Effect):
effect.Begin();
effect.Parameters["worldViewProjection"].SetValue(
worldMatrix * viewMatrix * projectionMatrix);
Цикл проходит через каждую сторону skybox’а:
for (int x = 0; x < 6; x++)
{
Сделаем простой тест видимости для каждой стороны skybox’а:
float f=0;
switch(x)
{
case 0:
f = Vector3.Dot(vCameraDirection,Vector3.Forward);
break;
case 1:
f = Vector3.Dot(vCameraDirection,Vector3.Backward);
break;
case 2:
f = Vector3.Dot(vCameraDirection,Vector3.Up);
break;
case 3:
f = Vector3.Dot(vCameraDirection,Vector3.Down);
break;
case 4:
f = Vector3.Dot(vCameraDirection,Vector3.Right);
break;
case 5:
f = Vector3.Dot(vCameraDirection,Vector3.Left);
break;
}
Если данная сторона видна, то устанавливаем текстуру и рендерим:
if (f <= 0)
{
IGraphicsDeviceService graphicsService = (IGraphicsDeviceService)
Game.Services.GetService(typeof(IGraphicsDeviceService));
GraphicsDevice device = graphicsService.GraphicsDevice;
device.VertexDeclaration = vertexDecl;
device.Vertices[0].SetSource(vertices, 0,
vertexDecl.GetVertexStrideSize(0));
device.Indices = indices;
effect.Parameters["baseTexture"].SetValue(textures[x]);
effect.Techniques[0].Passes[0].Begin();
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
0,x*4,4,x*6,2);
effect.Techniques[0].Passes[0].End();
}
}
effect.End();
}
Skybox должен быть нарисован одним из первых (сделаем нулевым по порядку):
public int DrawOrder
{
get { return 0; }
}
public event EventHandler DrawOrderChanged;
public bool Visible
{
get { return true; }
}
public event EventHandler VisibleChanged;
#endregion
}
Давайте теперь взглянем на класс игры и на то, как используется компонент skybox’а:
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
ContentManager content;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
Создаем skybox:
SkyBox sb = new SkyBox(this);
sb.ContentManager = this.content;
sb.CameraDirection = Vector3.Forward;
sb.CameraPosition = Vector3.Zero;
sb.ViewMatrix = Matrix.CreateLookAt(sb.CameraPosition,
sb.CameraPosition + sb.CameraDirection, Vector3.Up);
Viewport viewport = graphics.GraphicsDevice.Viewport;
float aspectRatio = (float)viewport.Width / (float)viewport.Height;
sb.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
MathHelper.PiOver4, aspectRatio, 1.0f, 10000.0f);
Игровой компонент должен вызывать метод Initialize() внутренне. Хотя я и не использую его на своем компьютере, я добавлю его здесь:
Не забудьте добавить skybox к списку игровых компонентов:
this.Components.Add(sb);
}
}
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
content.Unload();
}
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
Для проверки правильности работы skybox’а, я добавил кое-какой код, который будет вращать камеру в зависимости от оси левого джойстика:
SkyBox sb = (SkyBox)this.Components[0];
Matrix matX = Matrix.CreateRotationY(
-GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.X * .05f);
sb.CameraDirection = Vector3.TransformNormal(sb.CameraDirection, matX);
Matrix matY = Matrix.CreateFromAxisAngle(
Vector3.Normalize(Vector3.Cross(sb.CameraDirection,Vector3.Up)),
-GamePad.GetState(PlayerIndex.One).ThumbSticks.Left.Y * .05f);
Vector3 vDir = Vector3.TransformNormal(sb.CameraDirection, matY);
if(Math.Abs(Vector3.Dot(Vector3.Up,vDir)) > 0.9f)
vDir = sb.CameraDirection;
sb.CameraDirection = vDir;
sb.ViewMatrix = Matrix.CreateLookAt(sb.CameraPosition,
sb.CameraPosition + sb.CameraDirection,Vector3.Up);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
}
}
}
Скачать Пример простого SkyBox для XNA 1.0
Скачать Пример простого SkyBox для XNA 3.1
Оригинал статьи здесь. Перевод предоставлен сайтом Russian Ziggyware.
#SkyBox, #уроки, #XNA, #основы
22 августа 2009
Комментарии [2]