contorno effetto oggetto


26

Come posso ottenere un effetto contorno simile a quelli presenti in League of Legends o Diablo III?

Profilo di League of Legends Profilo di League of Legends Schema di Diablo III

È fatto usando uno shader? Come?
Preferirei risposte che non sono legate a un particolare motore ma che posso adattare a qualsiasi motore su cui sto lavorando.

Risposte:


19

Si dovrà avere per rendere l'oggetto per due volte ad un certo punto. Si può ottenere via con il rendering solo i volti di fronte alla telecamera una volta e le facce rivolte via dalla fotocamera, una volta, ma ha i suoi compromessi.

La soluzione comune più semplice viene eseguita eseguendo il rendering dell'oggetto due volte nello stesso passaggio:

  • Si utilizza uno shader di vertice per invertire le normali dell'oggetto e "ingrandirlo" per le dimensioni del contorno e uno shader di frammenti per renderlo nel colore del contorno
  • Oltre quel rendering di contorno, si esegue il rendering dell'oggetto normalmente. L'ordine z di solito è automaticamente corretto, più o meno, poiché il contorno è costituito dalle facce che si trovano nella "parte posteriore" dell'oggetto mentre la figura stessa è composta da facce rivolte verso la telecamera.

Questo è abbastanza semplice da costruire e implementare ed evita qualsiasi trucco da render a texture, ma presenta alcuni inconvenienti evidenti:

  • La dimensione del contorno, se non la ridimensionate in base alla distanza dalla fotocamera, varierà. Gli oggetti più lontani avranno un contorno più piccolo di quelli vicini. Certo, questo potrebbe essere quello che vuoi davvero .
  • Lo shader di vertice "blow up" non funziona molto bene per oggetti complessi come lo scheletro nel tuo esempio, introducendo facilmente nel rendering manufatti che combattono z. La correzione richiede il rendering dell'oggetto in due passaggi, ma consente di evitare di invertire le normali.
  • Il contorno e l'oggetto potrebbero non funzionare molto bene quando altri oggetti occupano lo stesso spazio e in generale sono un dolore da ottenere se combinati con shader di riflessione e rifrazione.

L'idea di base per un tale shader è simile a questa (Cg, per Unity - il codice è un toon shader leggermente modificato che ho trovato da qualche parte e non ho notato la fonte, quindi è una prova di concetto più mal scritta di un pronto- shader da usare):

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

L'altro metodo comune esegue il rendering anche dell'oggetto due volte, ma evita completamente lo shader di vertice. D'altra parte, non può essere facilmente eseguito in un singolo passaggio e ha bisogno di render-to-texture: renderizza l'oggetto una volta con uno shader di frammenti di contorno "piatto" e usa una sfocatura (ponderata) su questo rendering in spazio dello schermo , quindi renderlo come al solito sopra di esso.

C'è anche un terzo e forse il metodo più semplice da implementare, sebbene tassi un po 'di più la GPU e farà desiderare ai tuoi artisti di ucciderti nel sonno a meno che non sia facile per loro generare: Avere gli oggetti hanno il contorno come un separato mesh tutto il tempo, solo completamente trasparente o spostato da qualche parte dove non è visto (come deep underground) fino a quando non ne hai bisogno


Lo stencil buffer non è un approccio comunemente usato qui?
edA-qa mort-ora-y

1
@ edA-qamort-ora-y: anche questo potrebbe funzionare, ma non ho mai provato questo approccio, quindi non posso commentarlo. :) Se hai in mente un algoritmo funzionante, sentiti libero di aggiungere questo metodo anche come un'altra risposta.
Martin Sojka,

Non ne so molto di più da solo, solo che i contorni sono spesso citati in riferimento ai buffer di stencil. :) Sembra che potrebbe essere solo una versione più basata su hardware del tuo primo approccio (due passaggi, primo uno più grande).
edA-qa mort-ora-y

L'approccio del buffer di stencil può utilizzare una maggiore larghezza di banda per l'aggiornamento e la cancellazione dei buffer di stencil e richiede passaggi multipli. L'approccio alle liste Martin può essere eseguito in un passaggio in alcuni casi limitati, due al massimo, e richiede un sovraccarico minimo di larghezza di banda.
Sean Middleditch,

Questo approccio (che fa esplodere le dimensioni lungo le normali) non funziona con oggetti come i cubi (specialmente con la fotocamera ortogonale). Qualche soluzione a questo?
NPS

4

Oltre alla risposta di Martin Sojkas, per oggetti statici (o sprite) puoi cavartela con qualcosa di più semplice.

Puoi salvare lo stesso sprite ma con il contorno nell'atlante della trama o in un'altra trama, il che rende semplice il passaggio. Ciò ti consentirà anche di creare contorni personalizzati che sono più visivamente accattivanti o semplicemente diversi.

L'altro metodo consiste nel salvare lo sprite come una forma di un colore leggermente più grande e renderlo appena prima del rendering dello sprite stesso. Ciò ti darà la possibilità di cambiare facilmente il colore della selezione e potresti non aver bisogno di forme di colore così diverse come avresti bisogno di sprite di contorno con il metodo n. 1.

Entrambi aumenteranno tuttavia il tuo ingombro di memoria.


4

Come sottolineato nei commenti alla risposta di Martin Sojka, un effetto simile può essere ottenuto anche utilizzando lo stencil o il buffer di profondità, come dettagliato da Max McGuire su FlipCode:

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

In pratica si tratta di disegnare una versione wireframe del modello che si desidera delineare con una maggiore ampiezza della linea (o nel caso in cui ciò non sia possibile, come in D3D ad esempio, utilizzando i quadratini rivolti verso la fotocamera per le linee) mentre si imposta il buffer dello stencil su un valore costante.

Questo approccio potrebbe essere un po 'datato usando OpenGL di oggi e per rendere sfocato il contorno dell'oggetto, sarà comunque necessario il rendering su texture.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.