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

В heightmap классе мы немного преобразуем код, чтобы сделать его аккуратней, и мы добавляем флаг, чтобы знать, используем ли мы heightmask и высоту, которую мы установим для каждой высоты в маске. Создавая случайный heightmap, мы проверяем, используем мы heightmask или нет. Если да, то мы устанавливаем высоту в маске, иначе, мы создаем его как раньше. Обратите внимание, что флаг проверяется в начале, чтобы вычислить heightmap быстрее, если мы не используем heightmask.

Вот код:

/// <summary>

/// Generate a random heightmap.

/// </summary>

public void GenerateRandomHeightmap()

{

Random random = new Random();

if (_useHeightmask)

{

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

{

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

{

if (_heightmask.GetMaskValue(x, z))

{

_heightValues[x + z * _width] = _maskedHeight;

}

else

{

_heightValues[x + z * _width] =

(float)random.NextDouble() *

(_maximumHeight — _minimumHeight) +

_minimumHeight;

}

}

}

}

else

{

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

{

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

{

_heightValues[x + z * _width] =

(float)random.NextDouble() *

(_maximumHeight — _minimumHeight) +

_minimumHeight;

}

}

}

}

Вы можете также обратить внимание, что класс Terrain был обновлен, теперь его легче использовать.

В файле GameFOT.cs можно видеть, что новая функция по имени «InitializeScene» заботится об инициализации сцены. Вместо того, чтобы загрузить уже сделанный heightmask, мы создаем custom pattern непосредственно в коде. У этого шаблона есть четыре плоских области в каждом углу.

Вот инициализация для го примера:

/// <summary>

/// Initialize scene.

/// </summary>

public void InitializeScene()

{

// Create the fly camera.

_flyCamera = new FlyCamera();

// We set the camera above the terrain.

_flyCamera.Position = new Vector3(0.0f, 6.0f, 0.0f);

_flyCamera.Rotation = new Vector3(0.5f, 0.0f, 0.0f);

// Create a heightmask data for a four player terrain as an example.

//

// Here’s the pattern:

//

// 0 0 0 0 0 0 0 0

// 0 X X 0 0 X X 0

// 0 X X 0 0 X X 0

// 0 0 0 0 0 0 0 0

// 0 0 0 0 0 0 0 0

// 0 X X 0 0 X X 0

// 0 X X 0 0 X X 0

// 0 0 0 0 0 0 0 0

int width = 32;

int depth = 32;

float min = 0.0f;

float max = 3.0f;

float maskedHeight = 0.0f;

int startAreaWidth = 8;

int startAreaDepth = 8;

int offsetFromBorder = 2;

bool[] maskData = new bool[width * depth];

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

{

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

{

// Top left corner.

maskData[(offsetFromBorder + x) +

width * (offsetFromBorder + z)] = true;

// Top right corner.

maskData[(width — (offsetFromBorder + x)) +

width * (offsetFromBorder + z)] = true;

// Bottom left corner.

maskData[(offsetFromBorder + x) + width *

(depth — (offsetFromBorder + z))] = true;

// Bottom right corner.

maskData[(width — (offsetFromBorder + x)) +

width * (depth — (offsetFromBorder + z))] = true;

}

}

// Create the heightmask with our pattern.

Heightmask heightmask = new Heightmask(width, depth, maskData);

// Create a random heightmap using our mask.

Heightmap heightmap = new Heightmap(width, depth, min, max, maskedHeight);

// Set the heightmap heightmask.

heightmap.Heightmask = heightmask;

// Set the heightmap to use the heightmask.

heightmap.UseHeightmask = true;

// Generate a random heightmap using the heightmask.

heightmap.GenerateRandomHeightmap();

// Create the terrain.

_terrain = new Terrain(heightmap);

}

Правая кнопка и/или обратная клавиша используются, чтобы переключить использование heightmask.

Вы можете найти исходный текст в папке P105Heightmask.

Перерыв

Давайте отдохнем и посмотрим на то, что мы уже сделали. Сначала мы занялись ландшафтом, создавая небольшую структуру и генерируя сетку. Затем мы добавили способ создания случайных heightmap’ов, очень хаотический способ. Мы создали функцию сохранения и загрузки для heightmap. Наконец, мы добавили поддержку heightmasks. Так что дальше? Во-первых, давайте поменяем уродливый интерполированный цвет вершины на что-нибудь получше. После этого мы увеличим размер ландшафта, чтобы лучше оценивать пейзаж. Также мы разделим ландшафт на участки, чтобы рисовать только видимые (и частично видимые) частки. Это ускорит рендеринг ландшафта. Как только мы это сделаем, мы сосредоточимся на продвинутом создании heightmap, используя рекурсивные алгоритмы, шум Перлина, расширенные heightmask’и и объединим все это.

Текстурируемый ландшафт

Картинка, стоящая тысячу слов:


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

В heightmap классе мы удаляем весь код, связанный с heightmask, и добавляем следующий конструктор:

/// <summary>

/// Default constructor for a prebuilt heightmap.

/// </summary>

/// <param name=»width»></param>

/// <param name=»depth»></param>

/// <param name=»min»></param>

/// <param name=»max»></param>

/// <param name=»data»></param>

public Heightmap(int width, int depth, float min, float max, float[] data)

{

_width = width;

_depth = depth;

_minimumHeight = min;

_maximumHeight = max;

_heightValues = (float[])data.Clone();

}

Класс FlyCamera немного обновлен из-за увеличенного размера ландшафта, теперь она движется быстрее. Input manager оставлен без изменений.

Большинство изменений коснулись класса Terrain. Во-первых, мы добавляем четыре текстуры, одна для уровня земли, одна для уровня грязи, одна для скалистого уровня и последняя для уровня визуальных помех. Уровень земли — самая низкая, визуальные помехи — самая высокая. Мы изменяем старый формат VertexPositionColor, привыкший к геометрии VertexPositionNormalTexture во всем коде.

Текстуры загружаются в методе LoadGraphicsContent:

/// <summary>

/// Load the graphics content and also build the mesh.

/// </summary>

public void LoadGraphicsContent()

{

_effect = GameFOT.Instance.ContentManager.Load<Effect>(

«Content/Effects/TerrainEffect»);

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

_groundTexture = GameFOT.Instance.ContentManager.Load<Texture2D>(

«Content/Textures/TerrainGround»);

_mudTexture = GameFOT.Instance.ContentManager.Load<Texture2D>(

«Content/Textures/TerrainMud»);

_rockTexture = GameFOT.Instance.ContentManager.Load<Texture2D>(

«Content/Textures/TerrainRock»);

_snowTexture = GameFOT.Instance.ContentManager.Load<Texture2D>(

«Content/Textures/TerrainSnow»);

BuildTerrainMesh();

}

Метод Draw теперь также ответственен за перемещение направленного света, используя шаблон круга. Кроме того, мы передаем программе построения теней эффекта vector3, названный «g_vecHeights». Этот вектор содержит высоты всех уровней текстуры.

/// <summary>

/// Draw the terrain.

/// </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;

if (_isWireframe)

{

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

}

else

{

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

}

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

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

Matrix worldViewProjection = _worldMatrix * viewMatrix * projectionMatrix;

Vector3 lightDirection = new Vector3(

-20.0f *

(float)Math.Sin(gameTime.TotalRealTime.TotalMilliseconds *

0.0001f),

0.0f,

-20.0f *

(float)Math.Cos(gameTime.TotalRealTime.TotalMilliseconds *

0.0001f));

GameFOT.Instance.GraphicsDevice.VertexDeclaration = _vertexDeclaration;

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

_effect.Parameters[«g_vecLightDirection»].SetValue(lightDirection);

_effect.Parameters[«g_vecHeights»].SetValue(new Vector3(14.0f, 21.0f, 28.0f));

_effect.Parameters[«g_texGround»].SetValue(_groundTexture);

_effect.Parameters[«g_texMud»].SetValue(_mudTexture);

_effect.Parameters[«g_texRock»].SetValue(_rockTexture);

_effect.Parameters[«g_texSnow»].SetValue(_snowTexture);

_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,

VertexPositionNormalTexture.SizeInBytes);

GameFOT.Instance.GraphicsDevice.Indices = _indexBuffer;

GameFOT.Instance.GraphicsDevice.DrawIndexedPrimitives(_primitiveType,

0,

0,

_geometry.Length,

vertexPerStrip * s,

primitivePerStrip);

pass.End();

}

}

_effect.End();

}

Метод BuildVertexBuffer обновлен, теперь мы можем вычислить нормаль вершины. Мы вычисляем каждую нормаль вершины, используя метод ComputeVertexNormal.

Вот новая функция:

/// <summary>

/// Compute normal for the given x,z coordinate.

/// </summary>

/// <param name=»x»></param>

/// <param name=»z»></param>

/// <param name=»normal»></param>

private void ComputeVertexNormal(int x, int z, out Vector3 normal)

{

int width = _heightmap.Width;

int depth = _heightmap.Depth;

Vector3 center;

Vector3 p1;

Vector3 p2;

Vector3 avgNormal = Vector3.Zero;

int avgCount = 0;

bool spaceAbove = false;

bool spaceBelow = false;

bool spaceLeft = false;

bool spaceRight = false;

Vector3 tmpNormal;

Vector3 v1;

Vector3 v2;

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

if (x > 0)

{

spaceLeft = true;

}

if (x < width — 1)

{

spaceRight = true;

}

if (z > 0)

{

spaceAbove = true;

}

if (z < depth — 1)

{

spaceBelow = true;

}

if (spaceAbove && spaceLeft)

{

p1 = new Vector3(x — 1, _heightmap.GetHeightValue(x — 1, z), z);

p2 = new Vector3(x — 1, _heightmap.GetHeightValue(x — 1, z — 1), z — 1);

v1 = p1 — center;

v2 = p2 — p1;

tmpNormal = Vector3.Cross(v1, v2);

avgNormal += tmpNormal;

++avgCount;

}

if (spaceAbove && spaceRight)

{

p1 = new Vector3(x, _heightmap.GetHeightValue(x, z — 1), z — 1);

p2 = new Vector3(x + 1, _heightmap.GetHeightValue(x + 1, z — 1), z — 1);

v1 = p1 — center;

v2 = p2 — p1;

tmpNormal = Vector3.Cross(v1, v2);

avgNormal += tmpNormal;

++avgCount;

}

if (spaceBelow && spaceRight)

{

p1 = new Vector3(x + 1, _heightmap.GetHeightValue(x + 1, z), z);

p2 = new Vector3(x + 1, _heightmap.GetHeightValue(x + 1, z + 1), z + 1);

v1 = p1 — center;

v2 = p2 — p1;

tmpNormal = Vector3.Cross(v1, v2);

avgNormal += tmpNormal;

++avgCount;

}

if (spaceBelow && spaceLeft)

{

p1 = new Vector3(x, _heightmap.GetHeightValue(x, z + 1), z + 1);

p2 = new Vector3(x — 1, _heightmap.GetHeightValue(x — 1, z + 1), z + 1);

v1 = p1 — center;

v2 = p2 — p1;

tmpNormal = Vector3.Cross(v1, v2);

avgNormal += tmpNormal;

++avgCount;

}

normal = avgNormal / avgCount;

}


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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s