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