Ландшафт с Геометрией Clipmaps в XNA (Перевод) Часть 5

Оригинал: http://www.ziggyware.com/readarticle.php?article_id=220

Теперь добавьте дополнительные переменные текстуры, которые мы будем использовать для карты нормалей normalmap и пейзажных текстур landscapetextures:

// Textures 
private Texture2D grassTexture; 
private Texture2D sandTexture; 
private Texture2D rockTexture; 
private Texture2D snowTexture; 
private Texture2D normalMap; 

Добавьте некоторую понравившуюся текстуру для вашего ландшафта к проекту или используете те, что я оставил в директории текстур и инициализации переменных в методе LoadContent.

grassTexture = Game.Content.Load<Texture2D>("Textures//dirt"); 
sandTexture = Game.Content.Load<Texture2D>("Textures//ground"); 
rockTexture = Game.Content.Load<Texture2D>("Textures//grass"); 
snowTexture = Game.Content.Load<Texture2D>("Textures//rock"); 

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

// Setup the effect with the values that wont change 
effect.Parameters["Grass"].SetValue(grassTexture); 
effect.Parameters["Sand"].SetValue(sandTexture); 
effect.Parameters["Rock"].SetValue(rockTexture); 
effect.Parameters["Snow"].SetValue(snowTexture); 
effect.Parameters["NormalMap"].SetValue(normalMap); 
// MapTexel is the multiplicative inverse of the mapsize. 
// This is used to map the normalmap correctly over the whole terrain. 
// Hint: one normalmap over whole terrainsize, so its 1.0/mapsize. 
effect.Parameters["MapTexel"].SetValue(1.0f / (float)mapsize); 
// TexTexel is used to span one texture over some Gridtiles. 
// Hint: one texture repeats after four tiles, so its 1.0/4.0 
effect.Parameters["TexTexel"].SetValue(1.0f / 4.0f); 

Наконец мы можем расширить наш shaderfile. Лучше мы создадим новый, так что удалите старый. Сначала мы определяем все переменные, которые используются в этом шейдере: Matrices, Lightsettings, Materialsettings и некоторые специальные параметры настройки clipmap. Также мы зададим пять текстур и их образцы texturesamplers. Упомяните один образец, который является зарезервированным для карты нормалей и имеет линейную интерполяцию.

//-------------------------------------- 
// Matrices and positions 
//-------------------------------------- 
uniform const float4x4 World; 
uniform const float4x4 View; 
uniform const float4x4 Projection; 
uniform const float3 EyePosition; 
//-------------------------------------- 
// Lights 
//-------------------------------------- 
uniform const float3 AmbientLightColor = 0.3; 
uniform const float3 LightDirection = float3(1, -1, 1); 
uniform const float3 LightDiffuseColor = 1; 
uniform const float3 LightSpecularColor = 1; 
//---------------------------------------- 
// Material 
//---------------------------------------- 
uniform const float3 DiffuseColor = 0.8f; 
uniform const float3 SpecularColor = 0.3f; 
uniform const float SpecularPower = 16; 
uniform const float SandStart = 0.0f; 
uniform const float GrassStart = 0.2f; 
uniform const float RockStart = 0.5f; 
uniform const float SnowStart = 0.9f; 
//---------------------------------------- 
// Terrain and Clipmapsettings 
//---------------------------------------- 
uniform const float G = 0; // Clip-Gridspacing 
uniform const float N = 15; // Clip-Size 
uniform const float S = 64; // Heightscale 
uniform const float TexTexel; // repeating texture per tile. 
uniform const float MapTexel; // 1.0f / mapsize. 
//---------------------------------- 
// Texture sampler 
//---------------------------------- 
uniform const Texture NormalMap; 
uniform const sampler SamplerNormalMap = sampler_state 
{ 
 texture = <NormalMap>; 
 magfilter = LINEAR; 
 minfilter = LINEAR; 
 mipfilter = LINEAR; 
 AddressU = BORDER; 
 AddressV = BORDER; 
 // normal outside of normalmap is zero 
 BorderColor = 0; 
}; 
uniform const Texture Grass; 
uniform const sampler SamplerGrass = sampler_state 
{ 
 texture = <Grass>; 
 magfilter = ANISOTROPIC; 
 minfilter = ANISOTROPIC; 
 mipfilter = ANISOTROPIC; 
 AddressU = WRAP; 
 AddressV = WRAP; 
}; 
uniform const Texture Rock; 
uniform const sampler SamplerRock = sampler_state 
{ 
 texture = <Rock>; 
 magfilter = ANISOTROPIC; 
 minfilter = ANISOTROPIC; 
 mipfilter = ANISOTROPIC; 
 AddressU = WRAP; 
 AddressV = WRAP; 
}; 
uniform const Texture Sand; 
uniform const sampler SamplerSand = sampler_state 
{ 
 texture = <Sand>; 
 magfilter = ANISOTROPIC; 
 minfilter = ANISOTROPIC; 
 mipfilter = ANISOTROPIC; 
 AddressU = WRAP; 
 AddressV = WRAP; 
}; 
uniform const Texture Snow; 
uniform const sampler SamplerSnow = sampler_state 
{ 
 texture = <Snow>; 
 magfilter = ANISOTROPIC; 
 minfilter = ANISOTROPIC; 
 mipfilter = ANISOTROPIC; 
 AddressU = WRAP; 
 AddressV = WRAP; 
}; 
uniform const Texture Slope; 
uniform const sampler SamplerSlope = sampler_state 
{ 
 texture = <Slope>; 
 magfilter = ANISOTROPIC; 
 minfilter = ANISOTROPIC; 
 mipfilter = ANISOTROPIC; 
 AddressU = WRAP; 
 AddressV = WRAP; 
}; 
//---------------------------------- 
// Structs 
//---------------------------------- 
struct ColorPair 
{ 
 float3 Diffuse; 
 float3 Specular; 
}; 
struct VertexShaderInput 
{ 
 // x, y and z position of a vertex. 
 // additionally w contains a second 
 // heightvalue to perform geomorphing. 
 float4 Position : POSITION0; 
}; 
struct VertexShaderOutput 
{ 
 // Position in projectionspace 
 float4 Position : POSITION0; 
 // Position in worldspace 
 float4 PositionWS : TEXCOORD0; 
 // Texture coordinates 
 float2 TexCoords : TEXCOORD1; 
 // Texture weights 
 float4 TexWeights : TEXCOORD2; 
}; 

Затем мы добавляем два метода. Один вычисляет освещение для нас. Он содержит основную модель освещения, которая также соблюдает зеркальное освещение. Та же самая формула, которая используется в шейдере BasicEffect из структуры XNA. Другой метод вычисляет морфинг фактор, чтобы поднять вершины и закрыть трещины между уровнями. Эта формула представлена в оригинальной статье по Geoclipmapping.

//---------------------------------- 
// Compute per-pixel lighting. 
// This is the same lighting like it 
// is used in the XNA Basic effect. 
// E: Eye-Vector 
// N: Unit vector normal in world space 
//---------------------------------- 
ColorPair ComputePerPixelLights(float3 E, float3 N) 
{ 
 ColorPair result; 
 result.Diffuse = AmbientLightColor; 
 result.Specular = 0; 
 float3 L = -LightDirection; 
 float3 H = normalize(E + L); 
 float2 ret = lit(dot(N, L), dot(N, H), SpecularPower).yz; 
 result.Diffuse += LightDiffuseColor * ret.x; 
 result.Specular += LightSpecularColor * ret.y; 
 result.Diffuse *= DiffuseColor; 
 result.Specular *= SpecularColor; 
 return result; 
} 
//---------------------------------- 
// Calculate the transition factor 
// Calculates the height morph factor of a position. 
// The further the position is away from the center, 
// the more it is morphed. 
//---------------------------------- 
float CalculateTransitionFactor(float2 pos, 
 float N, 
 float G, 
 float2 center) 
{ 
 float fac = N * G; 
 float2 term = abs(pos - center); 
 term -= ((fac - G) / 2 - fac / 10 - 2 * G); 
 term /= (fac / 10); 
 float result = max(term.x, term.y); 
 return clamp(result, 0, 1); 
} 

Наконец мы объединяем материал в вершине и шейдере pixelshader. Шейдер vertexshader сначала морфирует значение высоты между значениями «y» и «w» и заменяет затем значение «w» на 1.0f. Этот шаг перестановки очень важен. Если бы мы держали второе значение высоты там, мы бы получили неверное положения porjectionspace. Шейдер Vertexshader также создает некоторые веса для используемых текстур, поэтому у нас есть текстура песка sandtexture внизу ландшафта, трава и скалы в середине и снег на его вершине.

Вся работа, которую шейдер пикселей pixelshader должен сделать сейчас, это поиск нормали из карты нормалей и использование метода lightingmethod, чтобы получить значения для освещения lightingvalues. Затем он объединяет текстуры в зависимости от созданных весов textureweights, и применяет значения освещения. Результат — пиксель с попиксельным светом и смешанными структурами.

//---------------------------------- 
// Vertexshader 
//---------------------------------- 
VertexShaderOutput VertexShaderFunction(VertexShaderInput input) 
{ 
 VertexShaderOutput output = (VertexShaderOutput)0; 
 // Calculate the morphfactor to blend between the two heightvalues 
 float blend = CalculateTransitionFactor(input.Position.xz, 
 N, 
 G, 
 EyePosition.xz); 
 input.Position.y = lerp(input.Position.y, 
 input.Position.w, 
 blend); 
 // set the w value to 1 to get correct matrices. 
 input.Position.w = 1; 
 // Combine the matrices 
 float4 worldPosition = mul(input.Position, World); 
 float4 viewPosition = mul(worldPosition, View); 
 // Calculate the position in projection space 
 output.Position = mul(viewPosition, Projection); 
 // Get the position in worldspace 
 output.PositionWS.xyz = worldPosition.xyz; 
 // Calculate the texture coordinates. 
 output.TexCoords = input.Position.xz * TexTexel; 
 // Calculate textureweights to blend textures. 
 float h = input.Position.y / S; 
 output.TexWeights.x = saturate(1.0f - abs(h - SandStart) / 0.25f); 
 output.TexWeights.y = saturate(1.0f - abs(h - GrassStart) / 0.25f); 
 output.TexWeights.z = saturate(1.0f - abs(h - RockStart) / 0.25f); 
 output.TexWeights.w = saturate(1.0f - abs(h - SnowStart) / 0.25f); 
 output.TexWeights /= (output.TexWeights.x + output.TexWeights.y + 
 output.TexWeights.z + output.TexWeights.w); 
 return output; 
} 
//---------------------------------- 
// Pixelshader 
//---------------------------------- 
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 
{ 
 // Get normal form normalmap and a vector from camera to vertex 
 // to calculate lighting. 
 float3 N = tex2D(SamplerNormalMap, input.PositionWS.xz * MapTexel).xyz; 
 float3 E = normalize(EyePosition - input.PositionWS.xyz); 
 ColorPair light = ComputePerPixelLights(E, N); 
 // Combine the texture depend on texture weights. 
 float4 diffuse = 0; 
 diffuse += tex2D(SamplerSand, input.TexCoords) * input.TexWeights.x; 
 diffuse += tex2D(SamplerGrass, input.TexCoords) * input.TexWeights.y; 
 diffuse += tex2D(SamplerRock, input.TexCoords) * input.TexWeights.z; 
 diffuse += tex2D(SamplerSnow, input.TexCoords) * input.TexWeights.w; 
 // Apply lighting to current diffuse color. 
 diffuse.rgb *= light.Diffuse; 
 diffuse.rgb += diffuse.rgb * light.Specular * diffuse.r; 
 return diffuse; 
} 
//---------------------------------- 
// Techniques 
//---------------------------------- 
technique Technique1 
{ 
 pass Pass1 
 { 
 VertexShader = compile vs_1_1 VertexShaderFunction(); 
 PixelShader = compile ps_2_0 PixelShaderFunction(); 
 } 
} 

После того, как вы добавите свои любимые текстуры ландшафта и запустите код, вы увидите что-то похожее на следующее изображение. Я использовал свободно доступные текстуры от StudioEvil, которые очень хорошо смотрятся.

С этого момента вам решать, насколько сложным ваш ландшафт должен быть. Вы можете написать ваш собственный шейдер pixelshader, используя различные слои, а также карты: alphamaps, colormaps или что-нибудь еще. Вы можете использовать различные методы для различных уровней, как очень сложные с большим количеством текстур для мелкого уровня, так и держать внешний уровень покрытым одной единственной текстурой colortexture. Также возможно переместить часть, где обновляется вершина (updateVertex) в шейдер vertexshader, используя текстуры vertextextures. Таким образом, переданные данные графической карте будут представлять из себя только два значения: X-и Z- координаты. Поиск вершины heightlookup тогда будет выполняться в шейдере vertexshader.

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

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s