эффект контура объекта

26

Как я могу добиться эффекта контура, подобного тем, которые можно найти в League of Legends или Diablo III?

Контур Лиги Легенд Контур Лиги Легенд Набросок Diablo III

Это делается с помощью шейдера? Как?
Я бы предпочел ответы, которые не привязаны к какому-либо конкретному движку, а могут быть адаптированы к любому движку, над которым я работаю.

Жоао Портела
источник

Ответы:

19

Вы будете иметь , чтобы сделать объект дважды в какой - то момент. Вы можете уйти с оказанием только лица , стоящие перед камерой , как только и лица , стоящие вдали от камеры один раз, но у него есть свои компромиссы.

Самое простое распространенное решение достигается путем рендеринга объекта дважды за один проход:

  • Вы используете вершинный шейдер, чтобы инвертировать нормали объекта и «взорвать его» по размеру контура, и фрагментный шейдер, чтобы отобразить его в цвете контура.
  • На этом контуре рендеринга вы делаете объект как обычно. Z-порядок обычно автоматически более или менее правильный, поскольку контур создается гранями, которые находятся на «тыльной стороне» объекта, а сама фигура состоит из граней, обращенных к камере.

Это достаточно просто для создания и реализации и позволяет избежать любых уловок рендеринга в текстуру, но имеет несколько заметных недостатков:

  • Размер контура, если вы не масштабируете его по расстоянию от камеры, будет меняться. Объекты, расположенные дальше, будут иметь меньший контур, чем те, что находятся поблизости. Конечно, это может быть то, что вы на самом деле хотите .
  • Вершинный шейдер «взорвать» не очень хорошо работает для сложных объектов, таких как скелет в вашем примере, легко вводя артефакты z-борьбы в рендер. Исправление требует от вас рендеринга объекта в два прохода, но спасает вас от изменения нормалей.
  • Контур и объект могут работать не очень хорошо, когда другие объекты занимают одно и то же пространство, и, как правило, это трудно сделать правильно в сочетании с шейдерами отражения и преломления.

Основная идея для такого шейдера выглядит следующим образом (Cg, для Unity - код является слегка измененным toon-шейдером, который я нашел где-то и не заметил источника, так что это скорее плохо написанное доказательство концепции, чем готовый использовать шейдер):

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

Другой распространенный метод визуализирует объект также дважды, но полностью избегает вершинного шейдера. С другой стороны, это не может быть легко сделано за один проход и требует рендеринга в текстуру: визуализируйте объект один раз с помощью «плоского» контурного шейдера цветного контура и используйте (взвешенное) размытие для этого рендера в пространство на экране , а затем визуализировать объект как обычно поверх него.

Есть также третий и, возможно, самый простой в реализации метод, хотя он немного облагает налогом GPU и заставит ваших художников захотеть убить вас во сне, если вы не упростите их генерацию: у объектов есть контур как отдельный сетка все время, просто полностью прозрачная или перемещена куда-то, где ее не видно (например, глубоко под землей), пока она вам не понадобится

Мартин Сойка
источник
Разве трафаретный буфер не является здесь широко используемым подходом?
edA-qa mort-ora-y
1
@ edA-qamort-ora-y: Это может сработать, но я никогда не пробовал такой подход, поэтому не могу его комментировать. :) Если у вас есть рабочий алгоритм, не стесняйтесь добавить этот метод в качестве другого ответа.
Мартин Сойка
Я сам не знаю об этом больше, только то, что контуры часто упоминаются применительно к буферам трафарета. :) Кажется, это может быть просто более аппаратная версия вашего первого подхода (двухпроходная, первая большая).
edA-qa mort-ora-y
Подход буфера трафарета может использовать большую полосу пропускания при обновлении и очистке буферов трафарета и требует многократных проходов. Подход списков Мартина может быть выполнен за один проход в некоторых ограниченных случаях, максимум два, и требует минимальных накладных расходов полосы пропускания.
Шон Мидлдич
Этот подход (увеличение размера по нормали) не работает с такими объектами, как кубы (особенно с ортокамерой). Любое решение этого?
NPS
4

В дополнение к ответу Мартина Сойкаса, для статических объектов (или спрайтов) вы можете сделать что-то более простое.

Вы можете сохранить тот же спрайт, но с контуром, в свой текстурный атлас или другую текстуру целиком, что облегчает переключение. Это также позволит вам создавать пользовательские контуры, которые более привлекательны или выглядят иначе.

Другой метод - сохранить спрайт как одноцветную фигуру, которая немного больше, и визуализировать ее непосредственно перед рендерингом самого спрайта. Это даст вам возможность легко менять цвет выделения, и вам может не потребоваться столько разных цветовых форм, сколько вам понадобятся контурные спрайты с помощью метода # 1.

И то и другое увеличит вашу память.

Darcara
источник
4

Как указано в комментариях к ответу Мартина Сойки, аналогичного эффекта можно добиться также с помощью буфера трафарета или глубины, как это подробно описано Максом МакГуайром на FlipCode:

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

Это в основном рисование каркасной версии модели, которую вы хотите обрисовать в общих чертах с увеличенной шириной линии (или в случае, если это невозможно, как, например, в D3D, с использованием квадратов для линий, обращенных к камере), при установке буфера трафарета на постоянное значение.

Этот подход может быть немного устаревшим с использованием сегодняшнего OpenGL, и для того, чтобы размыть контур объекта, рендеринг в текстуру все равно будет необходим.

Koarl
источник