Que tal algo como isso?
Não desenhe sua iluminação pintando seus sprites de azulejos. Desenhe seus blocos apagados em um destino de renderização e depois as luzes do bloco em um segundo alvo de renderização, representando cada um como um retângulo em escala de cinza que cobre a área do bloco. Para renderizar a cena final, use um shader para combinar os dois destinos de renderização, escurecendo cada pixel do primeiro de acordo com o valor do segundo.
Isso produzirá exatamente o que você tem agora. Isso não ajuda, então vamos mudar um pouco.
Altere as dimensões do seu destino de renderização do lightmap para que cada bloco seja representado por um único pixel , em vez de uma área retangular. Ao compor a cena final, use um estado de amostrador com filtragem linear. Caso contrário, deixe tudo o mesmo.
Supondo que você tenha escrito seu shader corretamente, o lightmap deve ser efetivamente "ampliado" durante a composição. Isso proporcionará um bom efeito de gradiente gratuitamente através do amostrador de textura do dispositivo gráfico.
Você também pode cortar o sombreador e fazê-lo de maneira mais simples com um BlendState 'escurecido', mas precisaria experimentar com ele antes que eu pudesse fornecer os detalhes.
ATUALIZAR
Hoje tive tempo para zombar disso. A resposta acima reflete meu hábito de usar shaders como minha primeira resposta a tudo, mas nesse caso eles não são realmente necessários e seu uso complica desnecessariamente as coisas.
Como sugeri, você pode obter exatamente o mesmo efeito usando um BlendState personalizado. Especificamente, este BlendState personalizado:
BlendState Multiply = new BlendState()
{
AlphaSourceBlend = Blend.DestinationAlpha,
AlphaDestinationBlend = Blend.Zero,
AlphaBlendFunction = BlendFunction.Add,
ColorSourceBlend = Blend.DestinationColor,
ColorDestinationBlend = Blend.Zero,
ColorBlendFunction = BlendFunction.Add
};
A equação de mesclagem é
result = (source * sourceBlendFactor) blendFunction (dest * destBlendFactor)
Portanto, com o nosso BlendState personalizado, isso se torna
result = (lightmapColor * destinationColor) + (0)
O que significa que uma cor de origem em branco puro (1, 1, 1, 1) preservará a cor de destino, uma cor de origem em preto puro (0, 0, 0, 1) escurecerá a cor de destino em preto puro e qualquer um tom de cinza no meio escurecerá a cor de destino em uma quantidade intermediária.
Para colocar isso em prática, primeiro faça o que for necessário para criar seu mapa de luz:
var lightmap = GetLightmapRenderTarget();
Em seguida, basta desenhar sua cena apagada diretamente no backbuffer, como faria normalmente:
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
/* draw the world here */
spriteBatch.End();
Em seguida, desenhe o mapa de luz usando o BlendState personalizado:
var offsetX = 0; // you'll need to set these values to whatever offset is necessary
var offsetY = 0; // to align the lightmap with the map tiles currently being drawn
var width = lightmapWidthInTiles * tileWidth;
var height = lightmapHeightInTiles * tileHeight;
spriteBatch.Begin(SpriteSortMode.Immediate, Multiply);
spriteBatch.Draw(lightmap, new Rectangle(offsetX, offsetY, width, height), Color.White);
spriteBatch.End();
Isso multiplicará a cor de destino (ladrilhos apagados) pela cor de origem (mapa de luz), escurecendo adequadamente os ladrilhos apagados e criando um efeito de gradiente como resultado da escala da textura do mapa de luz no tamanho necessário.