Olá pessoal, tudo certo?
O post de hoje está fortemente vinculado ao de ontem. De forma simples, vamos sair dessa renderização …
… para esta …
Como faremos isso?
- mudaremos a cor de fundo;
- aplicaremos uma textura na malha correspondente ao terreno.
Na aplicação da textura, voltamos a utilizar HLSL. Para não me tornar repetitvo, emito alguns detalhes ou conceitos passados em posts anteriores. Entretanto, em caso de dúvidas, estamos aí ![]()
Lembre-se que você pode consultar todos os posts dessa série. Além disso, pode pegar todos os fontes em https://github.com/ElemarJR/VamosAprenderXNA
Se quiser, você também pode me encontrar no Twitter: Follow @elemarjr
Como mudei a cor de fundo?!
Mudar a cor de fundo é muito simples. Basta alterar o método Draw da classe Game para algo semelhante ao que segue:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
//..
}
E ficamos assim:
Bacana, não!?
Escolhendo uma textura para aplicar na malha
Já temos nosso “céu azul”. Agora, precisamos escolher uma textura para aplicar a malha.
Geralmente, fotos aproximadas do tipo “revestimento” que desejamos aplicar, sem marcas muito fortes, são boas candidatas. Para nossas montanhas, selecionei essa textura aqui:
Escolhi essa textura com “irregularidades” para destacar as imperfeições de nossa montanha. ![]()
De VertexPositionColor para VertexPositionNormalBuffer
Ontem, usamos VertexPositionColor por ser muito simples. Entretanto, para podermos usar texturas precisamos usar outra estrutura: VertexPositionNormalTexture.
VertexPositionNormalTexture exige algumas informações a mais. Para ser mais preciso, precisamos calcular as coordenadas UV e o vetor Normal.
Veja a nova versão de CreateVertices:
void CreateVertices()
{
this.Vertices = new VertexPositionNormalTexture[VerticesCount];
Vector3 offset = new Vector3((Width / 2f) * CellSize, 0, (Length / 2f) * CellSize);
for (int z = 0; z < Length; z++)
for (int x = 0; x < Width; x++)
{
var position = new Vector3(
x * this.CellSize,
Heights[x, z],
z * CellSize
) - offset;
var uv = new Vector2((float)x / Width, (float)z / Length);
this.Vertices[z * Width + x] =
new VertexPositionNormalTexture(
position,
Vector3.Zero,
uv
);
}
}
Na prática, estou “esticando a textura” para que ela cubra toda a malha.
Repare que estou optando por não calcular o vetor normal dos vértices. Isso porque o vetor normal de cada vértice será uma combinação das normais todos os triângulos em que o vértice participa.
Calculando as normais para cada vértice
Calcular as normais para cada vértice não é um processo tão complicado quando possa parecer inicialmente. Observe:
void ComputeNormals()
{
for (int i = 0; i < IndexCount; i += 3)
{
var v1 = Vertices[Indexes[i]].Position;
var v2 = Vertices[Indexes[i + 1]].Position;
var v3 = Vertices[Indexes[i + 2]].Position;
var normal = Vector3.Cross(
v1 - v2,
v1 - v3
);
normal.Normalize();
Vertices[Indexes[i]].Normal += normal;
Vertices[Indexes[i + 1]].Normal += normal;
Vertices[Indexes[i + 2]].Normal += normal;
}
for (int i = 0; i < VerticesCount; i++)
Vertices[i].Normal.Normalize();
}
Calculo a normal de todos os triângulos da malha e acumulo o resultado em cada vértice envolvido. Depois, percorro novamente a lista de vértices normalizando o resultado.
Esse método é evocado no contrutor da classe Terrain (apresentado no post anterior).
Escrevendo um Effect para aplicação da textura
Agora que temos todas as informações para nossa malha utilizar texturas, vamos escrever o Effect para aplicação. (Arquivo BasicTerrainEffect.fx)
Começamos assim:
float4x4 World;
float4x4 View;
float4x4 Projection;
float3 LightDirection = float3(1, -1, 0);
float TextureTiling = 6;
texture2D Texture;
sampler2D TextureSampler = sampler_state {
Texture = <Texture>;
AddressU = Wrap;
AddressV = Wrap;
MinFilter = Anisotropic;
MagFilter = Anisotropic;
};
Considere:
- World, View e Projection são parâmetros comuns que dispensam comentários.
- Como quero aplicar um sombreamento muito simples, baseado em uma luz direcional (imitando o sol), solicito apenas a direção da luz (Assumo 1,-1,0 como default).
- TextureTiling diz respeito a quantas vezes “repetirei” a textura no terreno. Por default, nosso mapeamento considera que a textura inteira será aplicada uma única vez. Com esse parâmetro, estou dando esse controle ao código cliente. Se nada for informado, faço 6 repetições, em cada direção da textura (você verá como isso é simples no Pixel Shader que mostro a seguir).
- No mapeamento da textura, declaro que caso ocorra uma coordenada UV que saia dos limites (0,1), devo recomeçar. Além disso, especifico que o sampler deverá suavizar (com uma espécie de blur) a textura.
Vamos seguir em frente, vamos analisar as estruturas de dados do Effect:
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : NORMAL0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : TEXCOORD1;
};
Nada demais aqui também. Apenas estou alertando que desejarei utilizar as coordenadas UV e vetor Normal correspondentes ao vértice. O VertexShader também não tem nada de especial:
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.Normal = input.Normal;
output.UV = input.UV;
return output;
}
A mágina acontece no PixelShader:
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float light = dot(
normalize(input.Normal),
normalize(LightDirection)
);
light = clamp(light + 0.4f, 0, 1);
float3 tex = tex2D(TextureSampler, input.UV * TextureTiling);
return float4(tex * light, 1);
}
Crio um sombreamento direcional simples (já falamos sobre isso nos posts passados) e pego a textura considerando o TextureTiling (sim, é só uma multiplicação)
E, era isso
Usando o Effect na classe Terrain
Já temos nosso Effect pronto. Para utilizar, precisamos carregar o mesmo no método LoadContent do Game e “informar” a classe Terrain. Repare como passo o effect como “novo” parâmetro do construtor de Terrain:
var effect = Content.Load<Effect>("BasicTerrainEffect");
effect.Parameters["Texture"].SetValue(Content.Load<Texture2D>("grass"));
ground = new Terrain(
Content.Load<Texture2D>("heightmap1"),
effect,
30, 4800, device
);
Lindo!
Obviamente, o construtor de terrain salva esse effect em um atributo (no post anterior, usava um BasicEffect) para que seja utilizado na rotina de Draw.
Por fim, basta alterar a rotina Draw do Terrain. Observe:
public void Draw(Matrix View, Matrix Projection)
{
Device.SetVertexBuffer(VertexBuffer);
Device.Indices = IndexBuffer;
Effect.Parameters["World"].SetValue(Matrix.Identity);
Effect.Parameters["View"].SetValue(View);
Effect.Parameters["Projection"].SetValue(Projection);
foreach (var pass in Effect.CurrentTechnique.Passes)
{
pass.Apply();
Device.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
0, 0,
VerticesCount, 0,
IndexCount / 3);
}
}
Lindo! Executando…
Por hoje, era isso
![]()






Jhon Petter Marques
05/08/2011
Olaaaaaa, mais uma veiz arrebento vlw.
.
to me matando no livro xna 3.0, e fala verdade ta difisil entender, agora com vc explicando aprendo de primeira,froids nao?
ae escreve um livro vo ser o primeiro a compra
pelo que entendi vc ta na pagina 298 do meu livro, proximo conteudo e
pepeline animated skeleton
num vejo a hora
t+++
para de posta nao to viciado no seu site,add fovoritos? YES
elemarjr
06/08/2011
Opa. Quem sabe um dia escrevo alguma coisa.
Por hora, fico feliz em estar ajudando.