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
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.
źródło
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.
źródło