Создание Ландшафта. Фокусирование на создании Ландщафта (Перевод) Часть 1

by Inverse
Перевод статьи http://www.ziggyware.com/readarticle.php?article_id=132

Введение

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

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

Начало

Как сказал Черчилль — на первом месте должны быть первые вещи. Прежде, чем играть с ландшафтом, мы нуждаемся в приложении, чтобы визуализировать его и управлять им. Не бойтесь, я сделал это для Вас, и Вы можете найти его в папке, называющейся P102GettingStarted. В коде у нас есть следующие элементы:

* Класс GameFOT

* Класс FlyCamera

* Класс Heightmap

* Класс InputManager

* Класс Terrain

Названия являются довольно прямыми.

Класс GameFOT, FOT расшифровывается как, Фокусирование на Ландшафте, реализовывает класс Game XNA. Он содержит все компоненты приложения и передает их проекту через public static свойство класса, который является экземпляром класса GameFOT.
Это свойство делает очень легким доступ к графическим устройствам. Пока что, в функции обновления мы просто обновляем камеру, и позволяем пользователю выходить. В функции рисования мы позволяем классу ландшафта рисовать самому. Никакого воображения.

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

/// <summary>

/// Обновление камеры.

/// </summary>

/// <param name=»gameTime»></param>

public void Update(GameTime gameTime)

{

// Получаем секунды, прошедшие с прошлого времени игры.

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

// Получаем угол вращения правого thumbstick.

_rotation.X += MathHelper.ToRadians(

-GameFOT.Instance.InputManager.CurrentGamePadState.ThumbSticks.Right.Y *

_turnSpeedModifier);

_rotation.Y += MathHelper.ToRadians(

GameFOT.Instance.InputManager.CurrentGamePadState.ThumbSticks.Right.X *

_turnSpeedModifier);

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

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.Left))

{

_rotation.Y += MathHelper.ToRadians(-1.0f * _turnSpeedModifier);

}

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.Right))

{

_rotation.Y += MathHelper.ToRadians(1.0f * _turnSpeedModifier);

}

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.Down))

{

_rotation.X += MathHelper.ToRadians(1.0f * _turnSpeedModifier);

}

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.Up))

{

_rotation.X += MathHelper.ToRadians(-1.0f * _turnSpeedModifier);

}

// Вычисляем передний и левый вектор от угла вращения.

Vector3 forward = Vector3.Normalize(new Vector3((float)Math.Sin(-_rotation.Y),

(float)Math.Sin(_rotation.X),

(float)Math.Cos(-_rotation.Y)));

Vector3 left = Vector3.Normalize(new Vector3((float)Math.Cos(_rotation.Y),

0.0f,

(float)Math.Sin(_rotation.Y)));

// Перемещаем камеру налево или направо относительно левого вектора камеры.

_position += left *

GameFOT.Instance.InputManager.CurrentGamePadState.ThumbSticks.Left.X *

_moveSpeedModifier *

elapsedTime;

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.A))

{

_position += left * -1.0f * _moveSpeedModifier * elapsedTime;

}

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.D))

{

_position += left * 1.0f * _moveSpeedModifier * elapsedTime;

}

// Двигаем камеру вверх или вниз относительно единичного верхнего вектора.

_position += Vector3.Up *

(-GameFOT.Instance.InputManager.CurrentGamePadState.Triggers.Left +

GameFOT.Instance.InputManager.CurrentGamePadState.Triggers.Right) *

_moveSpeedModifier *

elapsedTime;

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.Q))

{

_position += Vector3.Up * 1.0f * _moveSpeedModifier * elapsedTime;

}

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.E))

{

_position += Vector3.Up * -1.0f * _moveSpeedModifier * elapsedTime;

}

// Двигаем камеру вперед или назад относительно единичного переднего вектора.

_position -= forward *

GameFOT.Instance.InputManager.CurrentGamePadState.ThumbSticks.Left.Y *

_moveSpeedModifier *

elapsedTime;

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.S))

{

_position -= forward * -1.0f * _moveSpeedModifier * elapsedTime;

}

if (GameFOT.Instance.InputManager.CurrentKeyboardState.IsKeyDown(Keys.W))

{

_position -= forward * 1.0f * _moveSpeedModifier * elapsedTime;

}

// Вычисляем матрицу представления и проектирования.

_view = Matrix.CreateTranslation(-_position) *

Matrix.CreateRotationZ(_rotation.Z) *

Matrix.CreateRotationY(_rotation.Y) *

Matrix.CreateRotationX(_rotation.X);

_projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,

GameFOT.Instance.AspectRatio,

0.1f,

1000.0f);

}

Класс InputManager следит за текущими и предыдущими состояниями клавиатуры, мыши и джойстика. Он будет также служить местом для различных хелперов ввода, если мы будем нуждаться в них.

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

Последний по порядку, но не по значимости, класс Terrain. Этот класс используется, чтобы построить сеть ландшафта из heightmap, обновить ландшафт в случае необходимости (никакой потребности сейчас в этом нет) и также заботиться о рисунке ландшафта. Сеть ландшафта сделана с треугольными полосами.

Вот как он строит вершинный буфер:

/// <summary>

/// Строим вершинный буфера сети ландшафта.

/// </summary>

private void BuildVertexBuffer()

{

int width = _heightmap.Width;

int depth = _heightmap.Depth;

_worldMatrix = Matrix.CreateTranslation((float)width * -0.5f, 0.0f, (float)depth * -0.5f);

int index = 0;

Vector3 position;

Color color = Color.Yellow;

_geometry = new VertexPositionColor[width * depth];

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

{

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

{

position = new Vector3((float)x, _heightmap.GetHeightValue(x, z), (float)z);

_geometry[index] = new VertexPositionColor(position, color);

++index;

}

}

}

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

Вот как мы встраиваем индексный буфер.

/// <summary>

/// Создаем индексный буфер сети ландшафта.

/// </summary>

private void BuildIndexBuffer()

{

int width = _heightmap.Width;

int depth = _heightmap.Depth;

int stripLength = 4 + (depth — 2) * 2;

int stripCount = width — 1;

_indices = new short[stripLength * stripCount];

int index = 0;

for (int s = 0; s < stripCount; ++s)

{

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

{

_indices[index] = (short)(s + depth * z);

++index;

_indices[index] = (short)(s + depth * z + 1);

++index;

}

}

}

Сеть автоматически создается в методе LoadGraphicsContent каждый раз, когда мы должны пересобрать все содержимое, поэтому мы не устанавливаем вершинные и индексные буферы.

/// <summary>

/// Загружаем графическое содержимое и собираем сетку.

/// </summary>

public void LoadGraphicsContent()

{

_effect = GameFOT.Instance.ContentManager.Load<Effect>(«Content/Effects/TerrainEffect»);

_effect.CurrentTechnique = _effect.Techniques[«DefaultTechnique»];

BuildVertexBuffer();

_primitiveType = PrimitiveType.TriangleStrip;

_vertexDeclaration = new VertexDeclaration(GameFOT.Instance.GraphicsDevice,

VertexPositionColor.VertexElements);

_vertexBuffer = new VertexBuffer(GameFOT.Instance.GraphicsDevice,

VertexPositionColor.SizeInBytes * _geometry.Length,

ResourceUsage.WriteOnly,

ResourceManagementMode.Automatic);

_vertexBuffer.SetData<VertexPositionColor>(_geometry);

BuildIndexBuffer();

_indexBuffer = new IndexBuffer(GameFOT.Instance.GraphicsDevice,

sizeof(short) * _indices.Length,

ResourceUsage.WriteOnly,

IndexElementSize.SixteenBits);

_indexBuffer.SetData<short>(_indices);

}

Теперь, когда сетка собрана и загружена в память GPU, мы можем нарисовать ее.

/// <summary>

/// Рисуем ландшафт.

/// </summary>

/// <param name=»gameTime»></param>

/// <param name=»viewMatrix»></param>

/// <param name=»projectionMatrix»></param>

public void Draw(GameTime gameTime, Matrix viewMatrix, Matrix projectionMatrix)

{

int width = _heightmap.Width;

int depth = _heightmap.Depth;

int primitivePerStrip = (depth — 1) * 2;

int stripCount = width — 1;

int vertexPerStrip = depth * 2;

GameFOT.Instance.GraphicsDevice.RenderState.DepthBufferEnable = true;

GameFOT.Instance.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

GameFOT.Instance.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;

Matrix worldViewProjection = _worldMatrix * viewMatrix * projectionMatrix;

GameFOT.Instance.GraphicsDevice.VertexDeclaration = _vertexDeclaration;

_effect.Parameters[«g_matWorldViewProjection»].SetValue(worldViewProjection);

_effect.Begin();

for (int s = 0; s < stripCount; ++s)

{

foreach (EffectPass pass in _effect.CurrentTechnique.Passes)

{

pass.Begin();

GameFOT.Instance.GraphicsDevice.Vertices[0].SetSource(

_vertexBuffer,

0,

VertexPositionColor.SizeInBytes);

GameFOT.Instance.GraphicsDevice.Indices = _indexBuffer;

GameFOT.Instance.GraphicsDevice.DrawIndexedPrimitives(

_primitiveType,

0,

0,

_geometry.Length,

vertexPerStrip * s,

primitivePerStrip);

pass.End();

}

}

_effect.End();

}

Обратите внимание, что renderstate установлен в каркас, чтобы видеть сетку вместо цветного четырехугольника.

Кроме того, Вы вероятно обратили внимание, что мы используем эффект шейдера, чтобы рендерить ландшафт. Вот как используется шейдер вершин:

VS_OUTPUT VS_Transform(VS_INPUT suInput)

{

VS_OUTPUT suOutput;

suOutput.vPosition = mul(suInput.vPosition, g_matWorldViewProjection);

suOutput.vDiffuse = suInput.vDiffuse;

return suOutput;

}

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

Один комментарий на «Создание Ландшафта. Фокусирование на создании Ландщафта (Перевод) Часть 1»

  1. Руслан:

    Inverse, статья отличная, но не мог бы ты дать ссылку на исходник, где он есть? (ссылка выше битая) Спасибо.

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s