Umriss Objekt Wirkung

26

Wie kann ich einen ähnlichen Umrisseffekt erzielen wie in League of Legends oder Diablo III?

League of Legends-Übersicht League of Legends-Übersicht Diablo III Gliederung

Wird ein Shader verwendet? Wie?
Ich würde Antworten vorziehen, die nicht an einen bestimmten Motor gebunden sind, sondern die ich an jeden Motor anpassen kann, an dem ich arbeite.

João Portela
quelle

Antworten:

19

Sie wird das Objekt zweimal an einem gewissen Punkt machen müssen. Sie können mit Rendering nur die Gesichter vor der Kamera einmal und die Gesichter mit Blick weg weg einmal von der Kamera, aber es hat seine Kompromisse.

Die einfachste gängige Lösung besteht darin, das Objekt in einem Durchgang zweimal zu rendern:

  • Sie verwenden einen Vertex-Shader, um die Normalen des Objekts umzukehren und um die Größe des Umrisses zu "sprengen", und einen Fragment-Shader, um es in der Umrissfarbe zu rendern
  • Über diesem Umriss rendern Sie das Objekt normal. Die Z-Reihenfolge ist in der Regel mehr oder weniger automatisch richtig, da der Umriss durch die Gesichter auf der "Rückseite" des Objekts erstellt wird, während die Figur selbst aus Gesichtern besteht, die der Kamera zugewandt sind.

Dies ist einfach genug zu erstellen und zu implementieren und vermeidet Tricks beim Rendern von Texturen, weist jedoch einige auffällige Nachteile auf:

  • Die Umrissgröße variiert, wenn Sie sie nicht anhand des Abstands von der Kamera skalieren. Weiter entfernte Objekte haben einen kleineren Umriss als die in der Nähe befindlichen. Dies könnte natürlich das sein, was Sie tatsächlich wollen .
  • Der Vertex-Shader "Aufblasen" funktioniert bei komplexen Objekten wie dem in Ihrem Beispiel gezeigten Skelett nicht besonders gut, da Z-Fighting-Artefakte problemlos in das Rendering eingefügt werden können. Wenn Sie das Problem beheben möchten, müssen Sie das Objekt in zwei Durchgängen rendern, können jedoch die Normalen nicht umkehren.
  • Der Umriss und das Objekt funktionieren möglicherweise nicht sehr gut, wenn andere Objekte denselben Raum belegen und es im Allgemeinen schwierig ist, in Kombination mit Reflexions- und Brechungsshadern die richtigen Ergebnisse zu erzielen.

Die Grundidee für einen solchen Shader sieht wie folgt aus (Cg, for Unity - der Code ist ein leicht modifizierter Toon-Shader, den ich irgendwo gefunden habe und der die Quelle nicht notiert hat. zu verwendender Shader):

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

Die andere gebräuchliche Methode rendert das Objekt ebenfalls zweimal, vermeidet jedoch den Vertex-Shader vollständig. Auf der anderen Seite ist dies nicht einfach in einem Durchgang möglich und erfordert das Rendern in eine Textur: Rendern Sie das Objekt einmal mit einem "flachen", konturfarbenen Fragment-Shader und verwenden Sie eine (gewichtete) Unschärfe für dieses Rendern Platz auf dem Bildschirm , und rendern Sie das Objekt wie gewohnt darüber.

Es gibt auch eine dritte und möglicherweise am einfachsten zu implementierende Methode, die jedoch die GPU ein wenig belastet und Ihre Künstler dazu bringt, Sie im Schlaf zu ermorden, es sei denn, Sie erleichtern ihnen das Generieren: Lassen Sie die Objekte die Gliederung als separate haben Masche die ganze Zeit, nur vollständig transparent oder an einen Ort verschoben, an dem sie nicht zu sehen ist (wie tief im Untergrund), bis Sie sie brauchen

Martin Sojka
quelle
Ist der Stencil Buffer hier kein gängiger Ansatz?
edA-qa mort-ora-y
1
@ edA-qamort-ora-y: Das könnte auch funktionieren, aber ich habe diesen Ansatz nie ausprobiert, kann ihn also nicht kommentieren. :) Wenn Sie an einen funktionierenden Algorithmus denken, können Sie diese Methode auch als andere Antwort hinzufügen.
Martin Sojka
Ich weiß selbst nicht viel mehr darüber, nur, dass Umrisse häufig in Bezug auf Schablonenpuffer erwähnt werden. :) Es scheint, als wäre es nur eine hardwarebasierte Version Ihres ersten Ansatzes (zwei Durchgänge, der erste größere).
edA-qa mort-ora-y
Der Schablonenpuffer-Ansatz kann beim Aktualisieren und Löschen der Schablonenpuffer mehr Bandbreite verbrauchen und erfordert mehrere Durchläufe. Der Ansatz, den Martin Listen verwendet, kann in einigen wenigen Fällen in einem Durchgang durchgeführt werden, höchstens in zwei, und erfordert einen minimalen Mehraufwand an Bandbreite.
Sean Middleditch
Dieser Ansatz (Vergrößern der Größe entlang der Normalen) funktioniert nicht mit Objekten wie Würfeln (insbesondere mit Orthokameras). Irgendeine Lösung dafür?
NPS
4

Neben Martin Sojkas Antwort können Sie für statische Objekte (oder Sprites) etwas Einfacheres finden.

Sie können dasselbe Sprite aber mit dem Umriss in Ihrem Texturatlas oder einer anderen Textur vollständig speichern, was das Umschalten vereinfacht. Auf diese Weise können Sie auch benutzerdefinierte Konturen erstellen, die optisch ansprechender sind oder einfach anders aussehen.

Die andere Methode besteht darin, das Sprite als eine einfarbige Form zu speichern, die etwas größer ist, und diese zu rendern, bevor das Sprite selbst gerendert wird. Dies gibt Ihnen die Möglichkeit, die Farbe der Auswahl einfach zu ändern, und Sie benötigen möglicherweise nicht so viele verschiedene Farbformen, wie Sie mit Methode 1 Gliederungs-Sprites benötigen würden.

Beides erhöht jedoch Ihren Speicherbedarf.

Darcara
quelle
4

Wie in den Kommentaren zu Martin Sojkas Antwort ausgeführt, kann ein ähnlicher Effekt auch durch Verwendung der Schablone oder des Tiefenpuffers erzielt werden, wie von Max McGuire auf FlipCode ausgeführt:

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

Grundsätzlich wird eine Drahtmodellversion des gewünschten Modells mit vergrößerter Linienbreite gezeichnet (oder falls dies nicht möglich ist, wie beispielsweise in D3D, wenn Quads mit Kameraausrichtung für Linien verwendet werden), während der Schablonenpuffer auf einen konstanten Wert eingestellt wird.

Dieser Ansatz ist unter Verwendung der heutigen OpenGL-Technologie möglicherweise ein wenig veraltet. Damit der Objektumriss-Applear unscharf wird, ist das Rendern in die Textur weiterhin erforderlich.

Koarl
quelle