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

У программы построения теней TerrainEffect.fx есть новые однородные переменные для легкого управления светом, высотами уровня ландшафта, четырьмя текстурами и их образцами. Программа построения теней вершины преобразовывает вершину, вычисляет распространяемый цвет (который однороден и освещает ландшафт) и зеркальный цвет для снежного уровня. Кроме того, она вычисляет отношение каждой текстуры для каждой вершины и умножает его на освещающее значение, это сокращает координаты текстуры. Pixel shader просто применяет текстуры.

Вот effect shader:

// World view projection matrix.

uniform extern float4x4 g_matWorldViewProjection;

// A directional light.

uniform extern float3 g_vecLightDirection;

// A vector containing the different heights

// used to divide the terrain in ground, mud, rock and snow areas.

// Values are stored from the lowest to the highest heights.

uniform extern float3 g_vecHeights;

// Textures used for the terrain.

uniform extern texture g_texGround;

uniform extern texture g_texMud;

uniform extern texture g_texRock;

uniform extern texture g_texSnow;

// Texture samplers.

sampler2D g_smpGround = sampler_state

{

texture = <g_texGround>;

MinFilter = LINEAR;

MagFilter = LINEAR;

MipFilter = LINEAR;

AddressU = WRAP;

AddressV = WRAP;

};

sampler2D g_smpMud = sampler_state

{

texture = <g_texMud>;

MinFilter = LINEAR;

MagFilter = LINEAR;

MipFilter = LINEAR;

AddressU = WRAP;

AddressV = WRAP;

};

sampler2D g_smpRock = sampler_state

{

texture = <g_texRock>;

MinFilter = LINEAR;

MagFilter = LINEAR;

MipFilter = LINEAR;

AddressU = WRAP;

AddressV = WRAP;

};

sampler2D g_smpSnow = sampler_state

{

texture = <g_texSnow>;

MinFilter = LINEAR;

MagFilter = LINEAR;

MipFilter = LINEAR;

AddressU = WRAP;

AddressV = WRAP;

};

// Vertex shader input structure.

struct VS_INPUT

{

float4 vPosition : POSITION;

float3 vNormal : NORMAL;

float2 vTexCoord : TEXCOORD0;

};

// Vertex shader output structure.

struct VS_OUTPUT

{

float4 vPosition : POSITION;

float2 vTexCoord0 : TEXCOORD0;

float2 vTexCoord1 : TEXCOORD1;

float2 vTexCoord2 : TEXCOORD2;

float2 vTexCoord3 : TEXCOORD3;

float4 vDiffuse : COLOR0;

};

// Pixel shader input structure.

struct PS_INPUT

{

float2 vTexCoord0 : TEXCOORD0;

float2 vTexCoord1 : TEXCOORD1;

float2 vTexCoord2 : TEXCOORD2;

float2 vTexCoord3 : TEXCOORD3;

float4 vDiffuse : COLOR0;

};

VS_OUTPUT VS_Terrain(VS_INPUT suInput)

{

VS_OUTPUT suOutput = (VS_OUTPUT)0;

// Transform the vertices.

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

// Compute the diffuse color.

float diffuse = 0.3f * saturate(dot(g_vecLightDirection, suInput.vNormal)) + 0.7f;

// Compute the specular color.

float specular = saturate(3.0f * dot(g_vecLightDirection, suInput.vNormal));

// Get the height from the input position y component.

float height = suInput.vPosition.y;

// Compute the ratio of each texture for this vertex and multiply it by the lighting.

suOutput.vDiffuse.x = diffuse * saturate(100.0f * (g_vecHeights.x — height));

suOutput.vDiffuse.y = diffuse * saturate(100.0f * (g_vecHeights.y — height)) —

saturate(100.0f * (g_vecHeights.x — height));

suOutput.vDiffuse.z = diffuse * saturate(100.0f * (g_vecHeights.z — height)) —

saturate(100.0f * (g_vecHeights.y — height));

suOutput.vDiffuse.w = (diffuse + specular) * 0.95f *

saturate(100.0f * (height — g_vecHeights.z)) —

saturate(100.0f * (g_vecHeights.z — height));

// Scale down the texture coordinates and pass them to the pixel shader.

float2 coordinates = suInput.vTexCoord * 0.2f;

suOutput.vTexCoord0 = coordinates;

suOutput.vTexCoord1 = coordinates;

suOutput.vTexCoord2 = coordinates;

suOutput.vTexCoord3 = coordinates;

return suOutput;

}

float4 PS_Terrain(PS_INPUT suInput) : COLOR

{

// Sample the textures.

float4 ground = tex2D(g_smpGround, suInput.vTexCoord0);

float4 mud = tex2D(g_smpMud, suInput.vTexCoord1);

float4 rock = tex2D(g_smpRock, suInput.vTexCoord2);

float4 snow = tex2D(g_smpSnow, suInput.vTexCoord3);

// Compute the output color.

float4 color = ground * suInput.vDiffuse.x +

mud * suInput.vDiffuse.y +

rock * suInput.vDiffuse.z +

snow * suInput.vDiffuse.w;

return color;

}

technique DefaultTechnique

{

pass P0

{

VertexShader = compile vs_2_0 VS_Terrain();

PixelShader = compile ps_2_0 PS_Terrain();

}

}

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

/// <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(30.0f, 40.0f, 30.0f);

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

// Load the heightmap data in a color array..

Texture2D heightmapTexture = _contentManager.Load<Texture2D>(

«Content/Heightmaps/TerrainHeightmap»);

Color[] textureData = new Color[heightmapTexture.Width * heightmapTexture.Height];

heightmapTexture.GetData<Color>(textureData);

// Convert the data into a float array.

int width = heightmapTexture.Width;

int depth = heightmapTexture.Height;

float minimumHeight = 0.0f;

float maximumHeight = 20.0f;

float scale = 50.0f;

float[] data = new float[width * depth];

// When using a greyscale, Texture2D.GetData<>()

// will set the same value for all R, G and B components

// with an alpha component set to 255. The scale variable

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

{

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

{

data[x + width * z] = (float)textureData[x + width * z].R / 255.0f * scale;

}

}

// Create the heightmap.

Heightmap heightmap = new Heightmap(width, depth, minimumHeight, maximumHeight, data);

// Create the terrain.

_terrain = new Terrain(heightmap);

Исходный текст этой части может быть найден в папке P202TexturedTerrain.

Участки ландшафта

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

Вот два скриншота с ясно видимыми участками ландшафта:



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

Как мы отбираем участки ландшафта? Просто вычисляем ограничение frustum камеры, и проверяем на каждом участке, содержится ли его ограничивающий прямоугольник в frustum. Если участок ландшафта находится в frustum, мы просто рисуем его.

В качестве примера привожу два скриншота:



На первом скриншоте вы можете видеть, что весь ландшафт нарисован, поэтому все участки ландшафта в ограничении frustum камеры. На втором скриншоте вы можете видеть, что только подмножество ландшафта находится в frustum. В этом случае, 37 исправлений видимы или частично видимы.

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

Вот новый класс TerrainPatch:

/// <summary>

/// This is the terrain patch class.

/// </summary>

public class TerrainPatch

{

/// <summary>

/// Bounding box used for this patch.

/// </summary>

BoundingBox _boundingBox;

/// <summary>

/// Geometry of the terrain patch.

/// </summary>

VertexPositionNormalTexture[] _geometry;

/// <summary>

/// Indices of the terrain patch.

/// </summary>

short[] _indices;

/// <summary>

/// Vertex buffer of the terrain patch.

/// </summary>

VertexBuffer _vertexBuffer;

/// <summary>

/// Index buffer of the terrain patch.

/// </summary>

IndexBuffer _indexBuffer;

/// <summary>

/// Width of the terrain patch.

/// </summary>

int _width;

/// <summary>

/// Depth of the terrain patch.

/// </summary>

int _depth;

/// <summary>

/// X offset used when we retrieve the heigth values from the heightmap.

/// </summary>

int _offsetX;

/// <summary>

/// Y offset used when we retrieve the height values from the heightmap.

/// </summary>

int _offsetZ;

/// <summary>

/// Get the bounding box.

/// </summary>

public BoundingBox BoundingBox

{

get

{

return _boundingBox;

}

}

/// <summary>

/// Default constructor.

/// </summary>

public TerrainPatch()

{

_boundingBox = new BoundingBox();

}

/// <summary>

/// Build a terrain patch.

/// </summary>

/// <param name=»heightmap»></param>

/// <param name=»worldMatrix»></param>

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

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

/// <param name=»offsetX»></param>

/// <param name=»offsetZ»></param>

public void BuildPatch(Heightmap heightmap,

Matrix worldMatrix,

int width,

int depth,

int offsetX,

int offsetZ)

{

_width = width;

_depth = depth;

_offsetX = offsetX;

_offsetZ = offsetZ;

_boundingBox.Min.X = offsetX;

_boundingBox.Min.Z = offsetZ;

_boundingBox.Max.X = offsetX + width;

_boundingBox.Max.Z = offsetZ + depth;

BuildVertexBuffer(heightmap);

_vertexBuffer = new VertexBuffer(GameFOT.Instance.GraphicsDevice,

VertexPositionNormalTexture.SizeInBytes * _geometry.Length,

ResourceUsage.WriteOnly,

ResourceManagementMode.Automatic);

_vertexBuffer.SetData<VertexPositionNormalTexture>(_geometry);

BuildIndexBuffer();

_indexBuffer = new IndexBuffer(GameFOT.Instance.GraphicsDevice,

sizeof(short) * _indices.Length,

ResourceUsage.WriteOnly,

IndexElementSize.SixteenBits);

_indexBuffer.SetData<short>(_indices);

// Apply the world matrix transformation to the bounding box.

_boundingBox.Min = Vector3.Transform(_boundingBox.Min, worldMatrix);

_boundingBox.Max = Vector3.Transform(_boundingBox.Max, worldMatrix);

}

/// <summary>

/// Build the vertex buffer as well as the bounding box.

/// </summary>

/// <param name=»heightmap»></param>

private void BuildVertexBuffer(Heightmap heightmap)

{

int index = 0;

Vector3 position;

Vector3 normal;

_boundingBox.Min.Y = float.MaxValue;

_boundingBox.Max.Y = float.MinValue;

_geometry = new VertexPositionNormalTexture[_width * _depth];

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

{

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

{

float height = heightmap.GetHeightValue(x, z);

if (height < _boundingBox.Min.Y)

{

_boundingBox.Min.Y = height;

}

if (height > _boundingBox.Max.Y)

{

_boundingBox.Max.Y = height;

}

position = new Vector3((float)x, height, (float)z);

ComputeVertexNormal(heightmap, x, z, out normal);

_geometry[index] = new VertexPositionNormalTexture(position,

normal,

new Vector2(x, z));

++index;

}

}

}

/// <summary>

/// Build the index buffer.

/// </summary>

private void BuildIndexBuffer()

{

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;

}

}

}

/// <summary>

/// Compute vertex normal at the given x,z coordinate.

/// </summary>

/// <param name=»heightmap»></param>

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

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

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

private void ComputeVertexNormal(Heightmap heightmap, 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;

}

/// <summary>

/// Draw the terrain patch.

/// </summary>

public void Draw()

{

int primitivePerStrip = (_depth — 1) * 2;

int stripCount = _width — 1;

int vertexPerStrip = _depth * 2;

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

{

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

_vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);

GameFOT.Instance.GraphicsDevice.Indices = _indexBuffer;

GameFOT.Instance.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleStrip,

0,

0,

_geometry.Length,

vertexPerStrip * s,

primitivePerStrip);

}

}

}


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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s