Olá pessoal, como estamos?!
No post de hoje demonstro como criar um efeito projetor em XNA. Repare:
O logo está sendo projetado a partir da posição 1500,1500,1500 em direção a posição 0,150,0. Logo, qualquer objeto que seja “tocado” por esse vetor recebe a projeção.
Perceba que: se nossa nave “atravessar” a projeção, recebera também texturas.
Como você pode ver, não há teste de “cobertura”. A textura “atravessa” qualquer obstáculo. (Efeito diferente será mostrado em outros posts).
Para gerar os efeitos de hoje, usei novamente HLSL (considere ver o que já escrevi usando essa técnica). Se gosta de XNA, talvez seja interessante ver os posts anteriores dessa série.
O que é Projective Texturing?
Projective Texturing é uma técnica que “projeta” uma textura através de uma cena, como uma imagem sendo projetada em objetos, dentro de uma sala, por um projetor real.
Esta técnica é importante para simular efeitos como “um projetor” ou para trabalhar com iluminação não circular (como em um spot).
Projetar texturas é relativamente simples. Se você está acompanhando a série, já viu quase todo o necessário para fazer isso:
- cálculo de normais;
- indicação de profundidade;
- cálculo de luz;
- aplicação de texturas.
A única diferença, agora, é que usamos diferentes matrizes view e projection para calcular a “posição na tela” da textura que está sendo projetada. Para ser mais claro:
- calculamos a matriz view e projection para uma câmera virtual que está na posição do projetor apontando para o alvo;
- pegamos as coordenadas de tela de cada pixel;
- verificamos as cores correspondentes na textura que estamos projetando;
- adicionamos “essa cor” naquela calculada pela técnica de shading que estivermos adotando.
Para o post de hoje, utilizo apenas iluminação simples, com specular, apresentada na parte 10.
Escrevendo o Effect
Para podermos gerar a projeção indicada, precisamos escrever um effect que suporte isso. Comecemos pelos “parâmetros” que nosso Effect aceita necessários para cálculo da iluminação simples:
float4x4 World;
float4x4 View;
float4x4 Projection;
float3 DiffuseColor = float3(1,1,1);
float3 AmbientColor = float3(.1, .1, .1);
float3 LightDirection = float3(1,1,1);
float3 LightColor = float3(0.9, 0.9, 0.9);
texture BasicTexture;
sampler BasicTextureSampler = sampler_state {
texture = <BasicTexture>;
};
float SpecularPower = 32;
float3 SpecularColor = float3(1,1,1);
float3 CameraPosition;
bool TextureEnabled = false;
float ViewportWidth;
float ViewportHeight;
Aqui, nada diferente do que já haviamos visto na parte 10.
Agora, os parâmetros necessários para implementar nosso “projetor”.
float4x4 ProjectorViewProjection;
texture2D ProjectedTexture;
sampler2D ProjectorSampler = sampler_state
{
texture = <ProjectedTexture>;
};
bool ProjectorEnabled = false;
O que temos?! Basicamente, temos um parâmetro para a textura que desejamos projetar, outro para a matriz ViewProjection (world) correspondente a “câmera” posicionada no ponto projetor.
Além disso, implementei três funções auxiliares. Observe:
float2 ProjectionToScreen(float4 position)
{
float2 screenPos = position.xy / position.w;
return 0.5f * (float2(screenPos.x, -screenPos.y) + 1);
}
float2 halfPixel()
{
return 0.5f / float2(ViewportWidth, ViewportHeight);
}
float3 Project(float2 UV)
{
if (UV.x < 0 || UV.x > 1 || UV.y < 0 || UV.y > 1)
return float3(0,0,0);
return tex2D(ProjectorSampler, UV);
}
Onde:
- ProjetionToScreen – Converte uma coordenada 3D para uma coordenada de tela (2D);
- halfPixel – calcula a dimensão correspondente a metade de um pixel na tela;
- Project – Recebe uma coordenada UV e garante que, na eventualidade do parâmetro estar fora das coordenadas (0 e 1), a cor resultante do sampler seja preto.
Agora, as estruturas de input e output para o VertexShader:
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : NORMAL0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float3 Normal : TEXCOORD1;
float3 ViewDirection: TEXCOORD2;
float4 ProjectorScreenPosition : TEXCOORD4;
};
A novidade aqui é a posição do projetor. No mais, tudo igual ao que já usavamos para iluminação básica.
Por fim, Vertex e Pixel shaders:
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
output.UV = input.UV;
output.Normal = mul(input.Normal, World);
output.ViewDirection = worldPosition - CameraPosition;
output.ProjectorScreenPosition = mul(mul(input.Position, World),
ProjectorViewProjection);
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float3 color = DiffuseColor;
if (TextureEnabled)
color *= tex2D(BasicTextureSampler, input.UV);
float3 lighting = AmbientColor;
float3 lightDir = normalize(LightDirection);
float3 normal = normalize(input.Normal);
lighting += saturate(dot(lightDir, normal)) * LightColor;
float3 refl = reflect(lightDir, normal);
float3 view = normalize(input.ViewDirection);
lighting += pow(saturate(dot(refl, view)), SpecularPower) * SpecularColor;
float3 output = saturate(lighting) * color;
if (ProjectorEnabled)
output += Project(ProjectionToScreen(input.ProjectorScreenPosition) + halfPixel());
return float4(output, 1);
}
A novidade, em ambos os shaders está no final.
- Primeiro, calculo a posição de um ponto no mundo, conforme as matrizes da câmera onde está ocorrendo a projeção. Depois, transformo para a visão da “câmera” que está na emissão da textura.
- Depois, calculo a coordenada UV correspondente a posição do ponto com relação a essa posição;
- Usando essa coordenada UV, obtenho a cor do pixel correspondente na textura que estamos projetando;
- Por fim, “adiciono” a cor da textura a cor do pixel sendo processado.
Configurando o Efeito (no game)
Como vimos, o effect tem forte dependência da matriz correspondente a “câmera” posicionada no projetor. Por isso, ao carregar o efeito, fazemos sua configuração. Observe:
protected override void LoadContent()
{
#if DEBUG
this.Components.Add(new FpsGameComponent(this, graphics));
#endif
effect = Content.Load<Effect>("projection");
var viewport = graphics.GraphicsDevice.Viewport;
effect.Parameters["ViewportWidth"].SetValue(viewport.Width);
effect.Parameters["ViewportHeight"].SetValue(viewport.Width);
var texture = Content.Load<Texture2D>("xnalogo");
effect.Parameters["ProjectedTexture"].SetValue(texture);
var scale = 3.0f;
var projection = Matrix.CreateOrthographicOffCenter(
(-texture.Width / 2) * scale ,
(texture.Width / 2) * scale,
(-texture.Height / 2) * scale,
(texture.Height / 2) * scale,
-100000, 100000);
var view = Matrix.CreateLookAt(
new Vector3(1500, 1500, 1500),
new Vector3(0, 150, 0),
Vector3.Up
);
effect.Parameters["ProjectorViewProjection"].SetValue(view * projection);
effect.Parameters["ProjectorEnabled"].SetValue(true);
LoadSpaceship();
LoadTeapot();
LoadGround();
}
Como pode perceber, crio uma matriz projection e uma view para minha câmera de acordo com minha textura, posição do projetor e target.
A textura que utilizo é essa:
Por hoje, era isso.
![]()






ploobs
18/09/2011
Gostaria apenas de parabeniza-lo pela iniciativa de postar conteudos que alem basicao em portugues. A galera que ta comecando realmente precisa disso.
Sou um dos desenvolvedores da PloobsEngine (http://ploobs.com.br/?page_id=1381 / http://www.youtube.com/watch?feature=player_embedded&v=Pn0ezABdXlA), gostaria de conversar contigo. (se for do seu interesse, entre em contato)
elemarjr
18/09/2011
Grato pelo feedback. Vou entrar em contato.