outline_effect_part_1_featured

en Tutoriales, UE4

Efecto de contorno (Parte 1)

Con este tutorial vamos a crear un efecto de contorno básico que puede aplicarse sobre cualquier objeto 3D. Este efecto ha sido utilizado en múltiples juegos para mostrar que se puede interaccionar con el objeto o puede ser recogido.

Parte 1: Efecto de contorno
Parte 2: Oclusión, colores y resplandor
Parte 3: Zonas opacas
Parte 4: Límite de profundidad

lit_view

Lo primero que tenemos que hacer es entender los conceptos de Custom Depth Custom Stencil. La profundidad personalizada o custom depth es un buffer de profundidad introducido por Unreal Engine 4 junto con su PBR. Una textura de profundidad mantiene información sobre la distancia entre cada pixel individual de nuestro espacio y la cámara.

custom_depth_view

Podemos ver la representación de este buffer activado su visualización en el menu de nuestro nivel View Mode > Buffer Visualization > Custom Depth

custom_depth_visualization

El Custom Stencil es un buffer similar al Custom Depth que permite a renderizar las mallas como valores enteros. Con este buffer podemos procesar los objetos 3D como formas que cambian según la orientación de la cámara.

custom_stencil_view

Podemos ver este buffer usando el mismo menu que con el Custom Depth pero escogiendo Stencil. View Mode > Buffer Visualization > Custom Stencil

custom_stencil_visualization

Por defecto el renderizado del Custom stencil esta desactivado, para activarlo necesitamos ir a Window > Project Settings > Rendering > Post Process > Custom Depth-stencil Pass y elegir en el desplegable Enabled with Stencil.

custom_stencil_enable

Para establecer el valor del stencil en la malla tenemos que activar la opción Render CustomDepth Pass en la sección Rendering del objeto, y darle su valor en el campo CustomDepth Stencil Value.

custom_stencil_value_set

Otra alternativa es utilizar los nodos de blueprint:

custom_depth_nodes
Nodos Set render Custom Depth y Set custom Depth Stencil Value

Ahora ya podemos empezar con el material. En el momento de escribir este tutorial custom depth aun no funciona con materiales translucidos por lo que vamos a tener que trabajar con materiales opacos. En caso de necesitarlo una manera de conseguirlo es usar una segunda copia de la malla que utilice un material opaco a la que activaremos el Custom Depth y desactivaremos la opción de renderizarla.

El material que vamos a crear va a pertenecer al dominio de materiales post process.

material_details

Para determinar si el pixel pertenece al contorno tenemos que comprobar los valores de Stencil de los píxeles que lo rodean, teniendo en cuenta el grosor que queremos darle al contorno para realizar el desplazamiento de las coordenadas UV. Podemos crear una expresión Custom e insertar el código que hay a continuación para hacer esto. Si tienes alguna duda sobre como funcionan los nodos Custom puedes echar un ojo al tutorial anterior sobre HLSL y UE4

float offset_h = SceneTexelSize.r * Thickness;
float offset_v = SceneTexelSize.g * Thickness;

float TL = GetScreenSpaceData(ScreenPosition + float2(-offset_h, -offset_v), false).GBuffer.CustomStencil.r;
float TM = GetScreenSpaceData(ScreenPosition + float2(0, -offset_v), false).GBuffer.CustomStencil.r;
float TR = GetScreenSpaceData(ScreenPosition + float2(offset_h, -offset_v), false).GBuffer.CustomStencil.r;

float ML = GetScreenSpaceData(ScreenPosition + float2(-offset_h, 0), false).GBuffer.CustomStencil.r;
float MR = GetScreenSpaceData(ScreenPosition + float2(offset_h, 0), false).GBuffer.CustomStencil.r;

float BL = GetScreenSpaceData(ScreenPosition + float2(-offset_h, offset_v), false).GBuffer.CustomStencil.r;
float BM = GetScreenSpaceData(ScreenPosition + float2(0, offset_v), false).GBuffer.CustomStencil.r;
float BR = GetScreenSpaceData(ScreenPosition + float2(offset_h, offset_v), false).GBuffer.CustomStencil.r;

return max(TL, max(TM, max(TR, max(ML, max(MR, max(BL, max(BM, BR ) ) ) ) ) ) );

Desde la versión de motor 4.19, el nodo ScreenPosition devuelve las coordenadas ViewportUV en vez de las del BufferUV por lo que necesitamos llamar a la función ViewportUVToBufferUV para convertir las coordenadas de posición:

float offset_h = SceneTexelSize.r * Thickness;
float offset_v = SceneTexelSize.g * Thickness;

float TL = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, -offset_v)), false).GBuffer.CustomStencil.r;
float TM = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, -offset_v)), false).GBuffer.CustomStencil.r;
float TR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, -offset_v)), false).GBuffer.CustomStencil.r;

float ML = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, 0)), false).GBuffer.CustomStencil.r;
float MR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, 0)), false).GBuffer.CustomStencil.r;

float BL = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, offset_v)), false).GBuffer.CustomStencil.r;
float BM = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, offset_v)), false).GBuffer.CustomStencil.r;
float BR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, offset_v)), false).GBuffer.CustomStencil.r;

return max(TL, max(TM, max(TR, max(ML, max(MR, max(BL, max(BM, BR ) ) ) ) ) ) );

Desde la versión de motor 4.20 tenemos que comprobar si estamos utilizando propiedades de SceneTexture (como el acceso a la propiedad CustomStencil de GBuffer), para evitar errores de compilación tenemos que añadir la siguiente directiva de preprocesador

#if SCENE_TEXTURES_DISABLED
return 0;
#endif

Nuestro código final quedaría asi:

#if SCENE_TEXTURES_DISABLED
return 0;
#endif
float offset_h = SceneTexelSize.r * Thickness;
float offset_v = SceneTexelSize.g * Thickness;

float TL = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, -offset_v)), false).GBuffer.CustomStencil.r;
float TM = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, -offset_v)), false).GBuffer.CustomStencil.r;
float TR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, -offset_v)), false).GBuffer.CustomStencil.r;

float ML = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, 0)), false).GBuffer.CustomStencil.r;
float MR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, 0)), false).GBuffer.CustomStencil.r;

float BL = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(-offset_h, offset_v)), false).GBuffer.CustomStencil.r;
float BM = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(0, offset_v)), false).GBuffer.CustomStencil.r;
float BR = GetScreenSpaceData(ViewportUVToBufferUV(ScreenPosition + float2(offset_h, offset_v)), false).GBuffer.CustomStencil.r;

return max(TL, max(TM, max(TR, max(ML, max(MR, max(BL, max(BM, BR ) ) ) ) ) ) );

Tenemos que descartar los píxeles que están en el interior del contorno para quedarnos solo con el borde. Podemos aplicar también un color a la línea del contorno usando el valor calculado para el pixel actual como alpha entre el color del pixel y nuestro color de contorno.

Material_outline_only
M_Outline_Only

Para utilizar este material podemos definir una Instancia de Material y establecer los parámetros que queramos para nuestro contorno, como el grosor y color de línea.

material_instance
MI_Outline_Only

Esta instancia debe añadirse a la lista de materiales del Volumen Post Process, podemos activar la opción Infinite Extend si queremos aplicar el efecto al nivel entero, sin tener en cuenta los límites del volumen.

post-process-volume-settings

Ahora que nuestro efecto de contorno esta funcionando podemos añadirle alguna característica más como el tratamiento de la oclusión. Pero esto es algo que haremos en la siguiente parte de este tutorial.

result_outline_only

Tutorial files

2022/04/14 – Updated to UE5 5.00

Ayudanos con este blog!

El último año he estado dedicando cada vez más tiempo a la creación de tutoriales, en su mayoria sobre desarrollo de videojuegos. Si crees que estos posts te han ayudado de alguna manera o incluso inspirado, por favor considera ayudarnos a mantener este blog con alguna de estas opciones. Gracias por hacerlo posible!

Escribe un comentario

Comentario