efekt obiektu konturu

26

Jak mogę uzyskać efekt konturu podobny do tego w League of Legends lub Diablo III?

Zarys League of Legends Zarys League of Legends Zarys Diablo III

Czy to się robi przy użyciu modułu cieniującego? W jaki sposób?
Wolę odpowiedzi, które nie są powiązane z żadnym konkretnym silnikiem, ale takie, które mogę dostosować do dowolnego silnika, nad którym pracuję.

João Portela
źródło

Odpowiedzi:

19

Państwo będzie pewnym momencie musiał renderować obiekt dwukrotnie. Można uciec z wyprawą zaledwie twarze stoi aparat raz i twarze stojących z dala od aparatu raz, ale to ma swoje kompromisów.

Najprostszym powszechnym rozwiązaniem jest renderowanie obiektu dwukrotnie w tym samym przebiegu:

  • Używasz modułu cieniującego wierzchołek, aby odwrócić normalne wartości obiektu i „wysadzić go” o rozmiar konturu, a moduł cieniujący fragmenty renderuje go w kolorze konturu
  • W tym renderowaniu konturu obiekt jest renderowany normalnie. Kolejność Z jest zwykle automatycznie mniej więcej odpowiednia, ponieważ kontur jest tworzony przez twarze znajdujące się na „tylnej stronie” obiektu, podczas gdy sama figura składa się z twarzy skierowanych w stronę kamery.

Jest to wystarczająco proste do zbudowania i wdrożenia i pozwala uniknąć sztuczek renderowania do tekstury, ale ma kilka zauważalnych wad:

  • Rozmiar konturu, jeśli nie zostanie skalowany według odległości od aparatu, będzie się różnić. Obiekty znajdujące się dalej będą miały mniejszy kontur niż te w pobliżu. Oczywiście może to być to, czego naprawdę chcesz .
  • „Wysadzający” moduł cieniujący wierzchołek nie działa zbyt dobrze w przypadku złożonych obiektów, takich jak szkielet w twoim przykładzie, łatwo wprowadzając artefakty walki z do renderowania. Naprawienie go wymaga renderowania obiektu w dwóch przejściach, ale oszczędza ci odwrócenia normalnych.
  • Kontur i obiekt mogą nie działać zbyt dobrze, gdy inne obiekty zajmują tę samą przestrzeń i generalnie trudno jest je uzyskać w połączeniu z shaderem odbicia i refrakcji.

Podstawowy pomysł takiego shadera wygląda następująco (Cg, dla Unity - kod to nieco zmodyfikowany toon shader, który gdzieś znalazłem i nie zanotowałem źródła, więc jest to bardziej źle napisany dowód koncepcji niż gotowy shader do użycia):

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"
}

Druga popularna metoda renderuje obiekt również dwa razy, ale całkowicie unika cieniowania wierzchołków. Z drugiej strony, nie można tego łatwo zrobić w jednym przejściu i wymaga renderowania do tekstury: Renderuj obiekt raz za pomocą „płaskiego” cieniowania fragmentów w kolorze konturu i użyj (ważonego) rozmycia na tym renderowaniu w przestrzeń ekranu , a następnie wyrenderuj obiekt jak zwykle na nim.

Istnieje również trzecia i być może najłatwiejsza do zaimplementowania metoda, choć nieco bardziej obciąża GPU i sprawi, że twoi artyści będą chcieli cię zamordować we śnie, chyba że ułatwisz im generowanie: Niech obiekty mają zarys jako osobny siatki przez cały czas, po prostu w pełni przezroczyste lub przenoszone gdzieś, gdzie nie są widoczne (jak głęboko pod ziemią), dopóki ich nie potrzebujesz

Martin Sojka
źródło
Czy bufor wzornika nie jest tutaj powszechnie stosowanym podejściem?
edA-qa mort-ora-y
1
@ edA-qamort-ora-y: To może również działać, ale nigdy nie próbowałem tego podejścia, więc nie mogę tego komentować. :) Jeśli masz na myśli działający algorytm, dodaj tę metodę jako kolejną odpowiedź.
Martin Sojka,
Sam nie wiem dużo więcej na ten temat, tyle że często wspomina się o konturach w odniesieniu do buforów szablonów. :) Wygląda na to, że może to być bardziej sprzętowa wersja twojego pierwszego podejścia (dwa przejścia, pierwszy większy).
edA-qa mort-ora-y
Podejście do bufora szablonów może zużywać więcej przepustowości przy aktualizacji i czyszczeniu buforów szablonów i wymaga wielu przebiegów. Podejście Listy Martina można wykonać w jednym przejściu, w niektórych ograniczonych przypadkach, maksymalnie w dwóch, i wymaga to minimalnego obciążenia przepustowości.
Sean Middleditch,
To podejście (powiększenie rozmiaru wzdłuż normalnych) nie działa z obiektami takimi jak kostki (szczególnie z kamerą orto). Jakieś rozwiązanie tego?
NPS
4

Oprócz odpowiedzi Martina Sojkasa, dla obiektów statycznych (lub duszków) można uciec z czegoś prostszego.

Możesz zapisać tego samego duszka, ale z konturem w atlasie tekstur lub inną teksturą całkowicie, co ułatwia przełączanie. Umożliwi to również tworzenie niestandardowych konturów, które są bardziej atrakcyjne wizualnie lub po prostu wyglądają inaczej.

Inną metodą jest zapisanie duszka jako kształtu jednokolorowego, który jest nieco większy i renderowanie go tuż przed renderowaniem samego duszka. To da ci możliwość łatwej zmiany koloru zaznaczenia i możesz nie potrzebować tyle różnych kształtów kolorów, ile potrzebowałbyś konturów duszków metodą nr 1.

Oba zwiększą twój ślad pamięci.

Darcara
źródło
4

Jak wskazano w komentarzach do odpowiedzi Martina Sojki, podobny efekt można również osiągnąć za pomocą bufora szablonów lub głębokości, jak szczegółowo opisał Max McGuire na FlipCode:

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

Zasadniczo rysuje model szkieletowy modelu, który chcesz obrysować, ze zwiększoną szerokością linii (lub w przypadku, gdy nie jest to możliwe, jak na przykład w D3D, używając kwadratów skierowanych w stronę kamery), ustawiając bufor matrycy na stałą wartość.

To podejście może być nieco przestarzałe przy użyciu dzisiejszego OpenGL, a aby zamazać kontur jabłoni, renderowanie do tekstury będzie nadal konieczne.

Koarl
źródło