¿Cómo puedo lograr un suave efecto de iluminación 2D?

18

Estoy haciendo un juego basado en mosaicos 2D en XNA.

Actualmente mi rayo parece a este .

¿Cómo puedo conseguir que se vea como este ?

En lugar de que cada bloque tenga su propio tinte, tiene una superposición suave.

Asumo algún tipo de sombreador y paso los valores de iluminación de los mosaicos circundantes al sombreador, pero soy un principiante con sombreadores, así que no estoy seguro.

Mi iluminación actual calcula la luz y luego la pasa a SpriteBatchay dibuja con el parámetro de tinte. Cada mosaico tiene un Colorcálculo antes del sorteo en mi algoritmo de iluminación, que se utiliza para el tinte.

Aquí hay un ejemplo de cómo calculo actualmente la iluminación (también lo hago desde la izquierda, la derecha y la parte inferior, pero me cansé de hacer esto cuadro por cuadro ...)

ingrese la descripción de la imagen aquí

¡Así que conseguir y dibujar la luz hasta ahora no es un problema !

He visto innumerables tutoriales sobre niebla de guerra y el uso de superposiciones circulares gradientes para crear relámpagos suaves, pero ya tengo un buen método para asignar un valor de relámpago a cada mosaico, solo necesita ser suavizado entre ellos.

Entonces para revisar

  • Calcular iluminación (hecho)
  • Dibujar mosaicos (Listo, sé que necesitaré modificarlo para el sombreador)
  • Sombrear mosaicos (Cómo pasar valores y aplicar "gradiente")
Cyral
fuente

Respuestas:

6

¿Qué tal algo como esto?

No dibujes tu iluminación teñiendo tus sprites de azulejos. Dibuja tus fichas no iluminadas en un objetivo de representación, luego dibuja las luces de la ficha en un segundo objetivo de representación, representando cada una como un rectángulo en escala de grises que cubre el área de la ficha. Para renderizar la escena final, use un sombreador para combinar los dos objetivos de renderizado, oscureciendo cada píxel del primero de acuerdo con el valor del segundo.

Esto producirá exactamente lo que tienes ahora. Eso no te ayuda, así que cambiémoslo un poco.

Cambie las dimensiones de su objetivo de representación de mapa de luz para que cada mosaico esté representado por un solo píxel , en lugar de un área rectangular. Al componer la escena final, use un estado de muestra con filtrado lineal. De lo contrario, deje todo lo demás igual.

Suponiendo que ha escrito su sombreador correctamente, el mapa de luz debe "ampliarse" efectivamente durante la composición. Esto le proporcionará un agradable efecto de degradado de forma gratuita a través de la muestra de textura del dispositivo gráfico.

También es posible que pueda cortar el sombreador y hacer esto de manera más simple con un BlendState 'oscurecedor', pero tendría que experimentar con él antes de poder darle los detalles.

ACTUALIZAR

Hoy tuve algo de tiempo para burlarme de esto. La respuesta anterior refleja mi hábito de usar sombreadores como mi primera respuesta a todo, pero en este caso no son realmente necesarios y su uso complica innecesariamente las cosas.

Como sugerí, puede lograr exactamente el mismo efecto utilizando un BlendState personalizado. Específicamente, 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
}; 

La ecuación de fusión es

result = (source * sourceBlendFactor) blendFunction (dest * destBlendFactor)

Entonces, con nuestro BlendState personalizado, eso se convierte

result = (lightmapColor * destinationColor) + (0)

Lo que significa que un color de origen de blanco puro (1, 1, 1, 1) conservará el color de destino, un color de origen de negro puro (0, 0, 0, 1) oscurecerá el color de destino a negro puro, y cualquier el tono de gris intermedio oscurecerá el color de destino en una cantidad media.

Para poner esto en práctica, primero haga lo que tenga que hacer para crear su mapa de luz:

var lightmap = GetLightmapRenderTarget();

Luego, dibuje su escena apagada directamente en el backbuffer como lo haría normalmente:

spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
/* draw the world here */
spriteBatch.End();

Luego dibuje el mapa de luz usando el 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();

Esto multiplicará el color de destino (mosaicos no iluminados) por el color de origen (mapa de luz), oscureciendo adecuadamente los mosaicos no iluminados y creando un efecto de gradiente como resultado de que la textura del mapa de luz se escala al tamaño necesario.

Cole Campbell
fuente
Esto parece bastante simple, veré lo que puedo hacer más tarde. Intenté algo similar antes (rendericé el mapa de luz sobre todo lo demás y usé un sombreador borroso, pero no pude hacer que el objetivo de renderizado sea "transparente", tenía una superposición púrpura, pero quería que viera hasta la capa de mosaico
real
Después de configurar su destino de renderizado en el dispositivo, llame a device.Clear (Color.Transparent);
Cole Campbell
Aunque, en este caso, vale la pena señalar que su mapa de luz probablemente debería borrarse a blanco o negro, que son luz completa y oscuridad completa, respectivamente.
Cole Campbell
Creo que eso es lo que probé antes, pero todavía era púrpura, bueno, lo investigaré mañana cuando tenga algo de tiempo para trabajar en esto.
Cyral
Casi todo esto funcionó, pero se desvanece de negro a blanco, en lugar de negro a transparente. La parte del sombreador es en lo que estoy confundido, cómo escribir una para atenuar la escena original sin luz a la de la nueva.
Cyral
10

Bien, aquí hay un método muy simple para crear un rayo 2D simple y suave, renderizado en tres pasos:

  • Pase 1: el mundo del juego sin relámpagos.
  • Pase 2: el Rayo sin el mundo
  • Combinación de los pasos 1. y 2. que se muestran en la pantalla.

En el pase 1 dibujas todos los sprites y el terreno.

En el pase 2 dibujas un segundo grupo de sprites que son las fuentes de luz. Deberían tener un aspecto similar a este:
ingrese la descripción de la imagen aquí
inicialice el objetivo de renderizado con negro y dibuje esos sprites sobre él con la combinación máxima o aditiva.

En el pase 3, combina los dos pases anteriores. Hay múltiples formas diferentes de cómo se pueden combinar. Pero el método más simple y menos artístico es combinarlos a través de Multiply. Esto se vería así:
ingrese la descripción de la imagen aquí

API-Bestia
fuente
La cuestión es que ya tengo un sistema de iluminación, y las luces de punto no funcionan aquí debido a que algunos objetos necesitan luz para pasar y otros no.
Cyral
2
Bueno, eso es más complicado. Tendrá que emitir rayos para obtener sombras. Teraria usa grandes bloques para su terreno, por lo que no hay problema allí. Podría usar una proyección de rayos imprecisa, pero eso no se vería mejor que los terrarios y podría ajustarse aún menos si no ha bloqueado los gráficos. Para una técnica avanzada y de buen aspecto está este artículo: gamedev.net/page/resources/_/technical/…
API-Beast
2

El segundo enlace que publicó parece una especie de niebla de guerra , hay algunas otras preguntas sobre esto

Eso me parece una textura , donde "borras" partes de la superposición negra (estableciendo el alfa de esos píxeles a 0) a medida que el jugador avanza.

Yo diría que la persona debe haber usado un tipo de "borrado" donde el jugador ha explorado.

bobobobo
fuente
1
No es niebla de guerra, calcula el rayo de cada cuadro. El juego que señalé es similar a Terraria.
Cyral
Lo que quise decir es que podría implementarse de manera similar a la niebla de guerra
bobobobo el
2

Vértices claros (esquinas entre mosaicos) en lugar de mosaicos. Combina la iluminación en cada mosaico en función de sus cuatro vértices.

Haga pruebas de línea de visión a cada vértice para determinar qué tan iluminado está (puede hacer que un bloque detenga toda la luz o disminuya la luz, por ejemplo, cuente cuántas intersecciones a cada vértice combinadas con la distancia para calcular un valor de luz en lugar de usar un prueba binaria pura visible / no visible).

Al renderizar un mosaico, envíe el valor de luz para cada vértice a la GPU. Puede usar fácilmente un sombreador de fragmentos para tomar el valor de luz interpolado en cada fragmento en el sprite del mosaico para iluminar cada píxel sin problemas. Ha pasado suficiente tiempo desde que toqué GLSL que no me sentiría cómodo dando una muestra de código real, pero en pseudocódigo sería tan simple como:

texture t_Sprite
vec2 in_UV
vec4 in_Light // coudl be one float; assuming you might want colored lights though

fragment shader() {
  output = sample2d(t_Sprite, in_UV) * in_Light;
}

El sombreador de vértices simplemente tiene que pasar los valores de entrada al sombreador de fragmentos, nada ni remotamente complicado.

Obtendrá una iluminación aún más suave con más vértices (por ejemplo, si cada mosaico se representa como cuatro mosaicos secundarios), pero puede que no valga la pena el esfuerzo dependiendo de la calidad que esté buscando. Pruébalo de la manera más simple primero.

Sean Middleditch
fuente
1
line-of sight tests to each vertex? Eso va a ser costoso.
cenizas999
Puede optimizar con cierta inteligencia. Para un juego en 2D, puede hacer una sorprendente cantidad de pruebas de rayos contra una grilla estricta con bastante rapidez. En lugar de una prueba LOS directa, podría "fluir" (no puedo pensar en el término apropiado ahora mismo; noche de cerveza) los valores de luz sobre los vértices en lugar de hacer pruebas de rayos.
Sean Middleditch
Usar pruebas de línea de visión complicaría esto más, ya tengo la iluminación resuelta. Ahora, cuando envío los 4 vértices al sombreador, ¿cómo puedo hacer eso? Sé que no se siente cómodo dando una muestra de código real, pero si pudiera obtener un poco más de información, sería bueno. También edité la pregunta con más información.
Cyral