Creando pistas en la nieve en Unreal Engine 4


Si juegas juegos AAA modernos, es posible que hayas notado una tendencia a usar paisajes cubiertos de nieve. Por ejemplo, están en Horizon Zero Dawn , Rise of the Tomb Raider y God of War . En todos estos juegos, la nieve tiene una característica importante: ¡puedes dejar rastros en ella!

Gracias a esta interacción con el entorno, se mejora la inmersión del jugador en el juego. Hace que el entorno sea más realista, y seamos honestos: es simplemente interesante. ¿Por qué pasar largas horas creando una mecánica curiosa si solo puedes dejar que el jugador caiga al suelo y hacer ángeles de nieve?

En este tutorial aprenderá lo siguiente:

  • Cree huellas usando la captura de escena para enmascarar objetos cerca del suelo
  • Use una máscara con material del terreno para crear nieve deformable
  • Para optimizar, muestre huellas en la nieve solo al lado del jugador

Nota: se entiende que ya está familiarizado con los conceptos básicos de trabajar con Unreal Engine. Si eres nuevo, echa un vistazo a nuestra serie de tutoriales de Unreal Engine para principiantes .

Llegar al trabajo


Descargue materiales para este tutorial. Descomprímalos, vaya a SnowDeformationStarter y abra SnowDeformation.uproject . En este tutorial crearemos trazas con la ayuda de un personaje y varios cuadros.


Antes de comenzar, debe saber que el método de este tutorial solo guardará rastros en un área determinada, y no en todo el mundo, porque la velocidad depende de la resolución del renderizado objetivo.

Por ejemplo, si queremos almacenar rastros para un área grande, tendremos que aumentar la resolución. Pero también aumenta el impacto de la captura de escenas en la velocidad del juego y el tamaño de la memoria para el renderizado objetivo. Para la optimización, debe limitar el alcance y la resolución.

Después de lidiar con esto, descubramos qué se necesita para darse cuenta de las huellas en la nieve.

Implementación de huellas en la nieve.


Lo primero que necesita para crear trazas es el renderizado de destino . El objetivo de renderizado será una máscara en escala de grises, en la que el blanco indica la presencia de un rastro y el negro indica su ausencia. Luego podemos proyectar el renderizado objetivo en el suelo y usarlo para mezclar las texturas y cambiar los vértices.


Lo segundo que necesitamos es una forma de enmascarar solo los objetos que afectan la nieve. Esto se puede implementar renderizando primero los objetos en profundidad personalizada . A continuación, puede utilizar la captura de escena con el material del proceso posterior para enmascarar todos los objetos renderizados en profundidad personalizada. Luego puede mostrar la máscara en el renderizado de destino.

Nota: la captura de escena es esencialmente una cámara con la capacidad de generar un renderizado objetivo.

La parte más importante de capturar una escena es su ubicación. El siguiente es un ejemplo de un renderizado objetivo capturado desde una vista superior . Aquí, un personaje en tercera persona y las cajas están enmascarados.


A primera vista, una captura con una vista superior nos conviene. Los formularios parecen apropiados para las mallas, por lo que no debería haber ningún problema, ¿verdad?

En realidad no El problema de capturar desde una vista superior es que no captura nada bajo el punto más amplio. Aquí hay un ejemplo:


Imagina que las flechas amarillas van hasta el suelo. En el caso de un cubo y un cono, la punta de flecha siempre permanecerá dentro del objeto. Sin embargo, en el caso de una esfera, el punto emerge de ella cuando se acerca a la tierra. Pero según la cámara, la punta siempre está dentro de la esfera. Así se verá la esfera de la cámara:


Por lo tanto, la máscara de esfera será más grande de lo que debería, incluso si el área de contacto con la tierra es pequeña.

Además, este problema se complementa con el hecho de que nos resulta difícil determinar si el objeto se refiere a la tierra.


Para hacer frente a estos dos problemas, puede usar la captura desde la parte inferior .

Agarre inferior


La captura desde abajo es la siguiente:


Como puede ver, la cámara ahora captura el lado inferior, es decir, el que toca el suelo. Esto elimina el problema del "área más amplia" que aparece cuando se captura desde arriba.

Para determinar si un objeto toca el suelo, puede usar material de posprocesamiento para realizar una verificación de profundidad. Comprueba si la profundidad del objeto es mayor que la profundidad de la tierra y si está por debajo de un desplazamiento predeterminado. Si se cumplen ambas condiciones, podemos enmascarar este píxel.


A continuación se muestra un ejemplo dentro de un motor con una zona de captura de 20 unidades sobre el suelo. Tenga en cuenta que la máscara aparece solo cuando el objeto pasa por un cierto punto. También tenga en cuenta que la máscara se vuelve más blanca a medida que el objeto se acerca al suelo.


Primero, cree un material de procesamiento posterior para realizar una verificación de profundidad.

Crear material de prueba de profundidad


Para realizar una verificación de profundidad, debe usar dos amortiguadores de profundidad: uno para el suelo y otro para los objetos que afectan la nieve. Como capturar la escena solo ve la tierra, la profundidad de la escena inferirá profundidad para la tierra. Para obtener profundidad para los objetos, simplemente los renderizaremos en profundidad personalizada .

Nota: para ahorrar tiempo, ya he representado el carácter y los cuadros en profundidad personalizada. Si desea agregar otros objetos que afecten a la nieve, debe habilitar el Paso de profundidad personalizado de renderizado para ellos.

Primero, necesitas calcular la distancia de cada píxel al suelo. Abra Materials \ PP_DepthCheck y cree lo siguiente:


A continuación, debe crear una zona de captura. Para hacer esto, agregue los nodos resaltados:


Ahora, si el píxel está dentro de las 25 unidades de la Tierra, aparecerá en la máscara. El brillo de enmascaramiento depende de qué tan cerca esté el píxel del suelo. Haga clic en Aplicar y regrese al editor principal.

A continuación, debe crear una captura de escena.

Crear captura de escena


Primero, necesitamos un renderizado objetivo en el que capturar una captura de escena. Vaya a la carpeta RenderTargets y cree un nuevo Render Target llamado RT_Capture .

Ahora creemos una captura de escena. En este tutorial agregaremos la captura de escena al plano, porque más tarde necesitaremos un script para ello. Abra Blueprints \ BP_Capture y agregue Scene Capture Component 2D . Nómbralo SceneCapture .


Primero necesitamos establecer el turno de captura para que mire al suelo. Vaya al panel Detalles y configure la rotación en (0, 90, 90) .


Luego viene el tipo de proyección. Dado que la máscara es una representación 2D de la escena, debemos eliminar la distorsión de la perspectiva. Para hacer esto, establezca Proyección \ Tipo de proyección en Ortográfica .


A continuación, debemos decirle a la captura de escena en qué renderizado de destino grabar. Para hacer esto, seleccione el valor de RT_Capture para Scene Capture \ Texture Target .


Finalmente, necesitamos usar material de verificación de profundidad. Agregue PP_DepthCheck a las Características de representación \ Materiales de proceso posterior . Para que el postprocesamiento funcione, también necesitamos cambiar la captura de escena / origen de captura a color final (LDR) en RGB .


Ahora que la captura de escena está configurada, necesitamos especificar el tamaño del área de captura.

Establecer el tamaño del área de captura


Como es mejor usar resoluciones bajas para el renderizado de destino, necesitamos usar el espacio de manera eficiente. Es decir, debemos elegir qué área cubrirá un píxel. Por ejemplo, si las resoluciones del área de captura y el renderizado objetivo son las mismas, entonces obtenemos una relación 1: 1. Cada píxel cubrirá un área de 1 × 1 (en unidades del mundo).

Para las pistas en la nieve, no se requiere una relación 1: 1, porque lo más probable es que no necesitemos ese detalle. Recomiendo usar proporciones más grandes porque esto le permitirá aumentar el tamaño del área de captura a baja resolución. Pero no haga que la proporción sea demasiado grande, de lo contrario los detalles comenzarán a perderse. En este tutorial, utilizaremos una relación de 8: 1, es decir, el tamaño de cada píxel será de 8 × 8 unidades del mundo.

Puede cambiar el tamaño del área de captura cambiando la propiedad Captura de escena \ Ancho de orto . Por ejemplo, si desea capturar un área de 1024 × 1024, establezca el valor en 1024. Dado que estamos utilizando una relación de 8: 1, establezca el valor en 2048 (la resolución predeterminada del renderizado de destino es 256 × 256).


Esto significa que la captura de escena capturará el área de 2048 × 2048 . Tiene aproximadamente 20 × 20 metros.

El material del suelo también necesita acceso al tamaño de captura para proyectar correctamente el renderizado objetivo. La forma más fácil de hacer esto es guardar el tamaño de captura en la Colección de parámetros de material . Esto es esencialmente una colección de variables a las que puede acceder cualquier material.

Guardar tamaño de captura


Regrese al editor principal y vaya a la carpeta Materiales . Cree una colección de parámetros de material que estará en Materiales y texturas . Cámbiele el nombre a MPC_Capture y ábralo.

Luego cree un nuevo parámetro escalar y asígnele el nombre CaptureSize . No se preocupe por establecer su valor, lo haremos sin rodeos.


Regrese a BP_Capture y agregue los nodos resaltados al Evento BeginPlay . Establezca Colección en MPC_Capture y Nombre de parámetro en CaptureSize .


Ahora cualquier material puede obtener el valor de Ortho Width al leerlo desde el parámetro CaptureSize . Hasta ahora con la captura de la escena hemos terminado. Haga clic en Compilar y vuelva al editor principal. El siguiente paso es proyectar el renderizado objetivo en el suelo y usarlo para deformar el paisaje.

Deformación del paisaje


Abra M_Landscape y vaya al panel Detalles. Luego establezca las siguientes propiedades:

  • Para Dos caras, seleccione habilitado . Dado que la captura de la escena "mirará" desde abajo, solo verá las caras inversas de la tierra. Por defecto, el motor no representa las caras posteriores de las mallas. Esto significa que no almacenará la profundidad de la tierra en el búfer de profundidad. Para solucionar esto, necesitamos decirle al motor que renderice ambos lados de la malla.
  • Para la teselación D3D11, seleccione la teselación plana (también se pueden utilizar triángulos PN). La teselación dividirá los triángulos de malla en pequeños. En esencia, esto aumenta la resolución de la malla y nos permite obtener detalles más finos al cambiar los vértices. Sin esto, la densidad de los picos será demasiado baja para crear trazas creíbles.


Una vez que se activan las teselaciones, se activará el Desplazamiento mundial y el Multiplicador de teselaciones.


Tessellation Multipler controla la cantidad de teselación. En este tutorial, no conectaremos este nodo, es decir, usaremos el valor predeterminado ( 1 ).

World Displacement obtiene un valor vectorial que describe en qué dirección y cuánto mover el vértice. Para calcular el valor de este contacto, primero debemos proyectar el renderizado objetivo en el suelo.

Proyecto Target Render


Para proyectar el renderizado objetivo, debe calcular sus coordenadas UV. Para hacer esto, cree el siguiente esquema:


¿Qué está pasando aquí?

  1. Primero necesitamos obtener la posición XY del vértice actual. Como estamos capturando desde abajo, se da la vuelta a la coordenada X, por lo que debe voltearla hacia atrás (si tuviéramos que capturar desde arriba, no necesitaríamos esto).
  2. Esta parte realiza dos tareas. En primer lugar, centra el renderizado objetivo de tal manera que su centro esté en las coordenadas (0, 0) del espacio mundial. Luego convierte las coordenadas del espacio mundial al espacio UV.

A continuación, cree los nodos seleccionados y combine los cálculos anteriores como se muestra a continuación. Para Muestra de textura, seleccione RT_Capture .


Esto proyectará el renderizado objetivo en el suelo. Sin embargo, todos los vértices fuera del área de captura muestrearán los bordes del renderizado objetivo. Esto es realmente un problema porque el renderizado de destino solo debe usarse para vértices dentro del área de captura. Así es como se ve en un juego:


Para solucionar esto, necesitamos enmascarar todos los UV que están fuera del rango de 0 a 1 (es decir, el área de captura). Para esto, creé la función MF_MaskUV0-1 . Devuelve 0 si el UV transmitido está fuera del rango de 0 a 1 y devuelve 1 si está dentro de él. Multiplicando el resultado por el renderizado objetivo, realizamos el enmascaramiento.

Ahora que hemos proyectado el renderizado objetivo, podemos usarlo para mezclar colores y mover vértices.

Usar el renderizado de destino


Comencemos mezclando colores. Para hacer esto, simplemente conectamos 1-x a Lerp :


Nota: si no comprende por qué uso 1-x , le explicaré: esto es necesario para invertir el renderizado objetivo, para que los cálculos sean un poco más fáciles.

Ahora que tenemos un rastro, el color de la tierra se está volviendo marrón. Si no hay color, permanece blanco.

El siguiente paso es el desplazamiento de los vértices. Para hacer esto, agregue los nodos seleccionados y conecte todo de la siguiente manera:


Esto hará que todas las áreas nevadas suban 25 unidades. Las áreas sin nieve tienen un desplazamiento cero, lo que creará un sendero.

Nota: puede cambiar la altura de desplazamiento para aumentar o disminuir los niveles de nieve. También tenga en cuenta que DisplacementHeight es el mismo valor que el desplazamiento de captura. Cuando tienen el mismo significado, nos da la deformación exacta. Pero hay casos en los que necesita cambiarlos individualmente, por lo que los dejé como parámetros separados.

Haga clic en Aplicar y regrese al editor principal. Cree una instancia de BP_Capture en el nivel y dele las coordenadas (0, 0, -2000) para colocarla bajo tierra. Haga clic en Reproducir y deambule con las teclas W , A , S y D para deformar la nieve.


La deformación funciona, ¡pero no quedan rastros! Esto sucedió porque la captura sobrescribe el renderizado de destino cada vez que se realiza la captura. Necesitamos alguna forma de hacer que las pistas sean permanentes .

Crear rastros permanentes


Para crear persistencia, necesitamos otro renderizado de destino ( búfer constante ), en el que todos los contenidos de captura se guardarán antes de sobrescribir. Luego agregaremos un búfer constante a la captura (después de sobrescribirlo). Obtenemos un bucle en el que cada renderizado de destino escribe en otro. Así es como crearemos rastros de permanencia.


Primero, necesitamos crear un búfer constante.

Crear un búfer persistente


Vaya a la carpeta RenderTargets y cree un nuevo Render Target llamado RT_Persistent . En este tutorial no tenemos que cambiar los parámetros de textura, pero en su propio proyecto deberá asegurarse de que ambos renderizadores de destino usen la misma resolución.

A continuación, necesitamos material que copie la captura en un búfer permanente. Abra Materiales \ M_DrawToPersistent y agregue un nodo Muestra de textura . Seleccione la textura RT_Capture para ella y conéctela de la siguiente manera:


Ahora necesitamos usar el material de dibujo. Haga clic en Aplicar y luego abra BP_Capture . Primero, cree una instancia dinámica del material (más tarde tendremos que pasarle valores). Agregue los nodos resaltados al Evento BeginPlay :


Borrar renderizado Los nodos 2D de destino borran cada renderizado de destino antes de su uso.

Luego abra la función DrawToPersistent y agregue los nodos resaltados:


A continuación, debemos asegurarnos de que el dibujo en el búfer constante se realice en cada cuadro, porque la captura se produce en cada cuadro. Para hacer esto, agregue DrawToPersistent a la marca de evento .


Finalmente, necesitamos agregar un búfer persistente al renderizado de captura de destino.

Grabar de nuevo para capturar


Haga clic en Compilar y abra PP_DepthCheck . Luego agregue los nodos resaltados. Para Muestra de textura, establezca el valor en RT_Persistent :


Ahora que el objetivo se escribe entre sí, obtenemos rastros que quedan. Haga clic en Aplicar y luego cierre el material. ¡Haz clic en Jugar y comienza a dejar pistas!


El resultado se ve muy bien, pero el circuito resultante solo funciona para un área del mapa. Si va más allá del área de captura, los rastros dejarán de aparecer.


Puede resolver este problema moviendo el área de captura con el reproductor. Esto significa que las huellas siempre aparecerán alrededor del área en la que se encuentra el jugador.

Nota: a medida que se mueve la captura, se elimina toda la información fuera del área de captura. Esto significa que si regresa al área donde ya había rastros, entonces ya desaparecerán. En el siguiente tutorial, le mostraré cómo crear pistas parcialmente retenidas.

Movimiento de captura


Puedes decidir que es lo suficientemente simple como para vincular la posición de captura XY a la posición XY del jugador. Pero si lo hace, el renderizado objetivo comenzará a desdibujarse. Esto se debe a que estamos moviendo el renderizado de destino con un paso de menos de un píxel. Cuando esto sucede, la nueva posición de píxel es entre los píxeles. Como resultado, un píxel es interpolado por varios píxeles. Así es como se ve:


Para solucionar este problema, necesitamos mover la captura en pasos discretos. Calculamos el tamaño de píxel en el mundo y luego movemos la captura a pasos iguales a ese tamaño. Entonces cada píxel nunca estará entre el otro, por lo que el desenfoque no aparecerá.

Para comenzar, creemos un parámetro en el que se almacenará la ubicación de captura. El material de la Tierra lo necesitará para realizar cálculos de proyección. Abra MPC_Capture y agregue un parámetro vectorial llamado CaptureLocation .


A continuación, debe actualizar el material de tierra para usar el nuevo parámetro. Cierre MPC_Capture y abra M_Landscape . Modifique la primera parte de los cálculos de proyección de la siguiente manera:


Ahora el renderizado objetivo siempre se proyectará en la ubicación de captura. Haga clic en Aplicar y cierre el material.

A continuación, realizaremos el movimiento de captura con un paso discreto.

Movimiento discreto de captura de pasos


Para calcular el tamaño de píxel en el mundo, puede usar la siguiente ecuación:

(1 / RenderTargetResolution) * CaptureSize 

Para calcular la nueva posición, utilizamos la ecuación que se muestra a continuación para cada componente de la posición (en nuestro caso, para las coordenadas X e Y).

 (floor(Position / PixelWorldSize) + 0.5) * PixelWorldSize 

Ahora úselos en la captura de planos. Para ahorrar tiempo, creé la macro SnapToPixelWorldSize para la segunda ecuación. Abra BP_Capture y luego abra la función MoveCapture . A continuación, cree el siguiente diagrama:


Calculará la nueva ubicación y luego guardará la diferencia entre la ubicación nueva y la actual en MoveOffset . Si está utilizando una resolución diferente a 256 × 256, cambie el valor resaltado.

A continuación, agregue los nodos seleccionados:


Este circuito moverá la captura con el desplazamiento calculado. Luego guardará la nueva ubicación de captura en MPC_Capture para que pueda ser utilizada por el material del suelo.

Finalmente, necesitamos realizar una actualización de posición en cada cuadro. Cierre la función y agréguela a la marca de evento antes de DrawToPersistent MoveCapture .


Mover una captura es solo la mitad de la solución. También necesitamos mover el búfer constante. De lo contrario, la captura y el búfer persistente no estarán sincronizados y producirán resultados extraños.


Movimiento de búfer permanente


Para cambiar un buffer constante, necesitamos pasar el desplazamiento de desplazamiento calculado. Abra M_DrawToPersistent y agregue los nodos resaltados:


Debido a esto, el búfer constante se desplazará por el valor del desplazamiento transmitido. Como en el material de la tierra, necesitamos voltear la coordenada X y realizar el enmascaramiento. Haga clic en Aplicar y cierre el material.

Entonces necesita transferir el desplazamiento. Abra BP_Capture y luego abra la función DrawToPersistent . A continuación, agregue los nodos resaltados:


Así es como convertimos MoveOffset en espacio UV y luego lo pasamos al material de dibujo.

Haga clic en Compilar y luego cierre el plano. ¡Haz clic en Jugar y corre al contenido de tu corazón! No importa qué tan lejos corras, las huellas siempre permanecerán a tu alrededor.


¿A dónde ir después?


El proyecto terminado se puede descargar desde aquí.

No es necesario utilizar las pistas creadas en este tutorial solo para nieve. Incluso puede usarlos para cosas como hierba triturada (en el próximo tutorial le mostraré cómo crear una versión avanzada del sistema).

Si desea trabajar con paisajes y representaciones de objetivos, le recomiendo ver el video de Chris Murphy Building High-End Gameplay Effects con Blueprint . ¡Este tutorial te mostrará cómo crear un enorme láser que quema tierra y hierba!

Source: https://habr.com/ru/post/es416703/


All Articles