efecto de objeto de contorno

26

¿Cómo puedo lograr un efecto de contorno similar a los que se encuentran en League of Legends o Diablo III?

Esquema de League of Legends Esquema de League of Legends Esquema de Diablo III

¿Se hace usando un sombreador? ¿Cómo?
Preferiría respuestas que no estén vinculadas a ningún motor en particular, pero que pueda adaptar a cualquier motor en el que esté trabajando.

João Portela
fuente

Respuestas:

19

Usted va a tener que hacer dos veces el objeto en algún momento. Puede salirse con la representación solo de las caras que miran hacia la cámara una vez y las caras que miran hacia la cámara una vez, pero tiene sus desventajas.

La solución común más simple se realiza renderizando el objeto dos veces en la misma pasada:

  • Utiliza un sombreador de vértices para invertir las normales del objeto y "explotarlo" por el tamaño del contorno y un sombreador de fragmentos para representarlo en el color del contorno.
  • Sobre ese renderizado de contorno, renderiza el objeto normalmente. El orden z suele ser automáticamente correcto, más o menos, ya que el contorno está formado por las caras que se encuentran en la "parte posterior" del objeto, mientras que la figura en sí está compuesta por caras orientadas hacia la cámara.

Esto es lo suficientemente simple como para construir e implementar y evita cualquier truco de renderizado a textura, pero tiene algunos inconvenientes notables:

  • El tamaño del contorno, si no lo escala por la distancia desde la cámara, variará. Los objetos más alejados tendrán un contorno más pequeño que los cercanos. Por supuesto, esto podría ser lo que realmente quieres .
  • El sombreador de vértices "explotar" no funciona muy bien para objetos complejos como el esqueleto en su ejemplo, introduciendo fácilmente artefactos de lucha z en el render. Arreglarlo requiere que renderices el objeto en dos pasadas, pero te evita invertir las normales.
  • Es posible que el contorno y el objeto no funcionen muy bien cuando otros objetos ocupan el mismo espacio y, en general, son difíciles de corregir cuando se combinan con sombreadores de reflexión y refracción.

La idea básica para tal sombreador se ve así (Cg, para Unity: el código es un sombreador de toon ligeramente modificado que encontré en alguna parte y no noté la fuente, por lo que es una prueba de concepto más mal escrita que una lista) sombreador de uso):

Shader "Basic Outline" {
    Properties {
        _Color ("Main Color", Color) = (.5,.5,.5,1)
        _OutlineColor ("Outline Color", Color) = (1,0.5,0,1)
        _Outline ("Outline width", Range (0.0, 0.1)) = .05
        _MainTex ("Base (RGB)", 2D) = "white" { }
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        Pass {
            Name "OUTLINE"
            Tags { "LightMode" = "Always" }
CGPROGRAM
#pragma exclude_renderers gles
#pragma exclude_renderers xbox360
#pragma vertex vert

struct appdata {
    float4 vertex;
    float3 normal;
};

struct v2f
{
    float4 pos : POSITION;
    float4 color : COLOR;
    float fog : FOGC;
};
float _Outline;
float4 _OutlineColor;
v2f vert(appdata v)
{
    v2f o;
    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    float3 norm = mul ((float3x3)UNITY_MATRIX_MV, v.normal);
    norm.x *= UNITY_MATRIX_P[0][0];
    norm.y *= UNITY_MATRIX_P[1][1];
    o.pos.xy += norm.xy * _Outline;
    o.fog = o.pos.z;
    o.color = _OutlineColor;
    return o;
}
ENDCG
            Cull Front
            ZWrite On
            ColorMask RGB
            Blend SrcAlpha OneMinusSrcAlpha
            SetTexture [_MainTex] { combine primary }
        }
        Pass {
        Name "BASE"
        Tags {"LightMode" = "Always"}
CGPROGRAM
#pragma fragment frag
#pragma vertex vert
#pragma fragmentoption ARB_fog_exp2
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"

struct v2f {
    float4 pos : SV_POSITION;
    float2    uv            : TEXCOORD0;
    float3    viewDir        : TEXCOORD1;
    float3    normal        : TEXCOORD2;
}; 

v2f vert (appdata_base v)
{
    v2f o;
    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    o.normal = v.normal;
    o.uv = TRANSFORM_UV(0);
    o.viewDir = ObjSpaceViewDir( v.vertex );
    return o;
}

uniform float4 _Color;

uniform sampler2D _MainTex;
float4 frag (v2f i)  : COLOR
{
    half4 texcol = tex2D( _MainTex, i.uv );

    half3 ambient = texcol.rgb * (UNITY_LIGHTMODEL_AMBIENT.rgb);
    return float4( ambient, texcol.a * _Color.a );
}
ENDCG
    }
    }
    FallBack "Diffuse"
}

El otro método común representa el objeto dos veces también, pero evita el sombreador de vértices por completo. Por otro lado, no se puede hacer fácilmente en una sola pasada, y necesita renderizar a textura: renderice el objeto una vez con un sombreador de fragmentos de color de contorno "plano" y use un desenfoque (ponderado) en este renderizado en espacio en pantalla , luego renderice el objeto como de costumbre encima de él.

También hay un tercer método y posiblemente el más fácil de implementar, aunque grava un poco más a la GPU y hará que tus artistas quieran asesinarte mientras duermes, a menos que hagas que sea fácil generarlos: haz que los objetos tengan el contorno como un elemento separado malla todo el tiempo, simplemente transparente o movido a un lugar donde no se ve (como en el subsuelo) hasta que lo necesite

Martin Sojka
fuente
¿El buffer de plantilla no es un enfoque comúnmente utilizado aquí?
edA-qa mort-ora-y
1
@ edA-qamort-ora-y: Esto podría funcionar también, pero nunca probé ese enfoque, así que no puedo comentarlo. :) Si tiene en mente un algoritmo de trabajo, siéntase libre de agregar este método como otra respuesta también.
Martin Sojka
No sé mucho más sobre eso, solo que los contornos se mencionan con frecuencia en referencia a los buffers de plantilla. :) Parece que podría ser una versión más basada en hardware de su primer enfoque (dos pasadas, la primera más grande).
edA-qa mort-ora-y
El enfoque del búfer de la plantilla puede usar más ancho de banda para actualizar y borrar los búferes de la plantilla y requiere múltiples pases. El enfoque que Martin enumera se puede hacer de una vez en algunos casos limitados, dos como máximo, y requiere una sobrecarga mínima de ancho de banda.
Sean Middleditch
Este enfoque (aumentar el tamaño a lo largo de las normales) no funciona con objetos como cubos (especialmente con cámara ortopédica). ¿Alguna solución para eso?
NPS
4

Además de la respuesta de Martin Sojkas, para objetos estáticos (o sprites) puede salirse con la suya con algo más simple.

Puede guardar el mismo sprite pero con el contorno en su atlas de texturas u otra textura por completo, lo que facilita el cambio. Esto también le permitirá hacer contornos personalizados que sean más atractivos visualmente o que simplemente se vean diferentes.

El otro método es guardar el sprite como una forma de un color que es ligeramente más grande y renderizarla justo antes de que se renderice el sprite. Esto le dará la capacidad de cambiar fácilmente el color de la selección, y es posible que no necesite formas de color tan diferentes como necesitaría sprites de contorno con el método # 1.

Sin embargo, ambos aumentarán su huella de memoria.

Darcara
fuente
4

Como se señaló en los comentarios a la respuesta de Martin Sojka, también se puede lograr un efecto similar utilizando la plantilla o el búfer de profundidad, como detalla Max McGuire en FlipCode:

http://www.flipcode.com/archives/Object_Outlining.shtml

Básicamente, está dibujando una versión de estructura de alambre del modelo que desea delineado con un mayor ancho de línea (o en caso de que esto no sea posible, como en D3D, por ejemplo, usando quads de líneas para cámaras) mientras establece el búfer de la plantilla en un valor constante.

Este enfoque podría estar un poco anticuado usando el OpenGL de hoy y para que el contorno del objeto sea borroso, aún será necesario renderizar a textura.

Koarl
fuente