Você vai ter que processar o objeto duas vezes em algum ponto. Você pode renderizar apenas os rostos voltados para a câmera uma vez e os rostos voltados para longe da câmera uma vez, mas tem suas vantagens e desvantagens.
A solução comum mais simples é feita renderizando o objeto duas vezes na mesma passagem:
- Você usa um sombreador de vértice para inverter as normais do objeto e "ampliá-lo" pelo tamanho do contorno e um sombreador de fragmento para renderizá-lo na cor do contorno
- Sobre essa renderização de contorno, você renderiza o objeto normalmente. A ordem z é geralmente automaticamente correta, mais ou menos, já que o contorno é feito pelas faces que estão na parte de trás do objeto, enquanto a própria figura é composta de faces voltadas para a câmera.
Isso é simples o suficiente para criar e implementar e evita truques de renderização para textura, mas tem algumas desvantagens visíveis:
- O tamanho do contorno, se você não o escalar pela distância da câmera, variará. Objetos mais distantes terão um contorno menor do que os próximos. Claro, isso pode ser o que você realmente deseja .
- O sombreador de vértice "explodido" não funciona muito bem para objetos complexos, como o esqueleto no seu exemplo, introduzindo facilmente artefatos de combate z na renderização. Para corrigi-lo, é necessário renderizar o objeto em duas passagens, mas evita que você reverta as normais.
- O contorno e o objeto podem não funcionar muito bem quando outros objetos estão ocupando o mesmo espaço e, em geral, são um problema para acertar quando combinados com shaders de reflexão e refração.
A idéia básica para um sombreador se parece com a seguinte (Cg, para Unity - o código é um sombreador de toon ligeiramente modificado que encontrei em algum lugar e não anotei a fonte, portanto, é mais uma prova de conceito mal escrita do que uma shader de uso):
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"
}
O outro método comum renderiza o objeto duas vezes também, mas evita o sombreador de vértice completamente. Por outro lado, não pode ser feito facilmente em uma única passagem e precisa renderizar para textura: renderize o objeto uma vez com um sombreador de fragmento "plano" na cor de contorno e use um borrão (ponderado) nessa renderização em espaço na tela e , em seguida, renderize o objeto normalmente.
Há também um terceiro método e, possivelmente, mais fácil de implementar, embora trate um pouco mais a GPU e faça com que seus artistas o matem enquanto você dorme, a menos que você facilite a geração: Os objetos têm o esboço como separado malha o tempo todo, apenas totalmente transparente ou movido para algum lugar onde não seja visto (como no subsolo) até que você precise
Além da resposta de Martin Sojkas, para objetos estáticos (ou sprites) você pode se safar com algo mais simples.
Você pode salvar o mesmo sprite, mas com o contorno no atlas de textura ou em outra textura, o que facilita a troca. Isso também permitirá que você faça contornos personalizados que sejam mais atraentes visualmente ou que pareçam diferentes.
O outro método é salvar o sprite como uma forma de uma cor ligeiramente maior e renderizá-la imediatamente antes da renderização do sprite. Isso permitirá alterar facilmente a cor da seleção e talvez você não precise de tantas formas de cores diferentes quanto de sprites de contorno com o método nº 1.
Ambos aumentarão sua pegada de memória.
fonte
Conforme apontado nos comentários à resposta de Martin Sojka, um efeito semelhante também pode ser alcançado utilizando o buffer de estêncil ou profundidade, conforme detalhado por Max McGuire no FlipCode:
http://www.flipcode.com/archives/Object_Outlining.shtml
Basicamente, está desenhando uma versão em wireframe do modelo que você deseja delinear com maior largura de linha (ou, caso isso não seja possível, como no D3D, por exemplo, usando quads voltados para a câmera para linhas) enquanto define o buffer de estêncil para um valor constante.
Essa abordagem pode ser um pouco antiquada usando o OpenGL de hoje e, para que o contorno do objeto seja embaçado, a renderização na textura ainda será necessária.
fonte