Olá pessoal, como estamos?!
HLSL é bacana, embora tenha tornado essa série um pouco mais difícil de acompanhar. Isso ocorre devido ao fato de que estamos programando em um nível um pouco mais baixo. Estamos abrindo mão de algumas facilidades para atingir resultados superiores. No caso dessa série, isso significa fazer jogos com gráficos mais atraentes.
Você pode acompanhar essa série desde o início. Ou ainda, pode ver o que já foi escrito sobre HLSL.
Até aqui, mostrei como trabalhar com luzes direcionais, que são interessantes para representar luzes como a do sol, por exemplo. Agora, começo a mostrar como representar luzes provenientes de uma área específica. Hoje, vamos falar sobre Point Light Effect.
Uma vista aérea previlegiada
O código-fonte está disponível em https://github.com/ElemarJR/VamosAprenderXNA
Point light é uma luz que emana para todas as direções em torno de sí (formato esférico), como uma lâmpada, e perde intencidade conforme a distância aumenta. Considere:
Todo o código-fonte está disponível em https://github.com/ElemarJR/VamosAprenderXNA
Uma forma simples de pensar a implementação point light seria dividir a distância entre a luz e o objeto pela distância máxima que desejamos atingir. Depois, inverter o resultado (subtraindo de 1) e multiplicar o Lambertian Lighting por este.
Entretanto, no mundo real, o comportamento seria um pouco difente. Observe:
Ou seja, há atenuação da luz ocorre com menor intensidade próximo a fonte, mas, vai aumentando gradativamente a medida que a distância aumenta. A fórmula pode ser melhor descrita por:
Intensidade = 1 – (d / a)f
Onde:
A implementação do effect para point light é relativamente simples (dado toda a fundamentação que já desenvolvemos no post anterior). Comecemos pela definição dos parâmetros:
float4x4 World; float4x4 View; float4x4 Projection; float3 AmbientLightColor = float3(.05, .05, .05); float3 DiffuseColor = float3(.85, .85, .85); float3 LightPosition = float3(0, 1500, 1500); float3 LightColor = float3(1, 1, 1); float LightAttenuation = 3000; texture BasicTexture; sampler BasicTextureSampler = sampler_state { texture = <BasicTexture>; }; float SpecularPower = 32; float3 SpecularColor = float3(1,1,1); float3 CameraPosition; bool SpecularEnabled = true; bool TextureEnabled = true;
A novidade, com relação aos effects que desenvolvemos até aqui, fica por conta de LightPosition, LightColor e LightAttenuation. Penso que os significados estejam claros.
Duas considerações são necessárias:
Consideremos, por exemplo, a modificação da cor da luz para vemelho.
var pointLightEffect = Content.Load<Effect>("PointLightEffect"); pointLightEffect.Parameters["LightColor"].SetValue(new Vector3(1,0,0));
Vejamos o resultado
Se fosse amarelo:
As estruturas de dados para PointLightEffect são bem simples. Observe:
struct VertexShaderInput { float4 Position : POSITION0; float2 UV : TEXCOORD0; float3 Normal : NORMAL0; }; struct VertexShaderOutput { float4 Position : POSITION0; float2 UV : TEXCOORD0; float3 Normal : TEXCOORD1; float4 WorldPosition : TEXCOORD2; float3 ViewDirection : TEXCOORD3; };
O elemento novo, comparado ao Effect desenvolvido no post anterior, é WorldPosition. Essa informação será “calculada” no Vertex Shader e utilizada no Pixel Shader.
O pixel shader para esse effect também é bem semelhante ao desenvolvido no final do post anterior. Observe:
VertexShaderOutput VertexShaderFunction(VertexShaderInput input) { VertexShaderOutput output; float4 worldPosition = mul(input.Position, World); float4 viewPosition = mul(worldPosition, View); output.Position = mul(viewPosition, Projection); output.WorldPosition = worldPosition; output.UV = input.UV; output.Normal = mul(input.Normal, World); output.ViewDirection = worldPosition - CameraPosition; return output; }
Como havia informado na explicação para as estruturas, a novidade é a “passagem a diante” da informação relacionada a posição real do vértice na matriz World.
Por fim, mas de forma alguma menos importante, temos o nosso PixelShader. Observe:
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0 { float3 color = DiffuseColor; if (TextureEnabled) color *= tex2D(BasicTextureSampler, input.UV).rgb; float3 lighting = float3(0, 0, 0); lighting += AmbientLightColor; float3 direction = normalize(LightPosition - input.WorldPosition); float diffuse = saturate(dot(normalize(input.Normal), direction)); float d = distance(LightPosition, input.WorldPosition); float att = 1 - pow(clamp(d / LightAttenuation, 0, 1), 2); lighting += diffuse * att * LightColor; if (SpecularEnabled) { float3 refl = reflect(direction, normalize(input.Normal)); float3 view = normalize(input.ViewDirection); lighting += pow(saturate(dot(refl, view)), SpecularPower) * SpecularColor; } return float4(color * lighting, 1); }
Como você pode perceber, basicamente, calculei a luz usando a equação de Lambert e depois apliquei a atenuação conforme equação descrita no início desse post.
Considere que esse Effect deixa o cálculo do brilho specular (Phong) como um opcional. Na minha implementação, desliguei intencionalmente o cálculo de Specular para o piso.
Por hoje, era isso.
Caro Elemar,
Seu curso de XNA é muito legal, porém eu queria saber como fazer para ter dois pontos de luz na mesma cena, pois como aplica o SetEffect por objeto. Aparecer no ground(piso) duas fontes de luz.
Grato