контурний ефект об'єкта


26

Як я можу досягти ефекту контуру, подібного до тих, що знайдені в League of Legends або Diablo III?

«Ліга легенд» «Ліга легенд» Контур Діабло III

Це робиться за допомогою шейдера? Як?
Я вважаю за краще відповіді, які не пов'язані з будь-яким конкретним двигуном, але таким, який я можу адаптувати до будь-якого двигуна, над яким я працюю.

Відповіді:


19

Ви будете мати , щоб зробити об'єкт двічі в якій - то момент. Ви можете піти з рендерингу лише обличчя, що стоїть перед камерою один раз, і обличчя, що відводиться від камери один раз, але це має свої компроміси.

Найпростіше загальне рішення виконується шляхом надання об'єкта двічі за один і той же прохід:

  • Ви використовуєте вершинний шейдер, щоб перевернути нормалі об'єкта і "підірвати його" за розміром контуру та фрагментом шейдера, щоб передати його в колір контуру
  • За цим контурним візуалізацією об'єкт відображається нормально. Порядок z, як правило, автоматично правильний, більш-менш, оскільки контур складається з граней, які знаходяться на «задній частині» об'єкта, а сама фігура складається з облич, звернених до камери.

Це досить просто для створення та впровадження та дозволяє уникнути будь-яких хитрощів щодо текстури, але має кілька помітних недоліків:

  • Розмір контуру, якщо його не масштабувати на відстані від камери, буде змінюватися. Об'єкти, що знаходяться далі, матимуть менший контур, ніж поблизу. Звичайно, це може бути те, що ви насправді хочете .
  • Вершина шейдера "підірвати" не дуже добре справляється зі складними об'єктами, такими як скелет у вашому прикладі, легко вводячи артефакти z-боротьби у візуалізацію. Для її виправлення потрібно винести об’єкт за два проходи, але врятує вас від зміни норми.
  • Обриси та об'єкт можуть не дуже добре працювати, коли інші об'єкти займають той самий простір, і взагалі боляче виправитись у поєднанні з шейдерами відбиття та заломлення.

Основна ідея такого шейдера виглядає приблизно так (Cg, для Unity - код є дещо зміненим тоновим шейдером, який я десь знайшов і не відмітив джерело, тому це більш погано написане підтвердження концепції, ніж готовий- використовувати шейдер):

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

Підхід до буфера трафарету може використовувати більше пропускної здатності для оновлення та очищення буферів трафарету і вимагає декількох проходів. Переліки списків Мартіна можуть бути виконані за один прохід у деяких обмежених випадках, максимум у двох, і вимагає мінімальних накладних частот.
Шон Міддлічч

Такий підхід (підірвання розміру вздовж норм) не працює з такими предметами, як куби (особливо з ортокамерою). Будь-яке рішення для цього?
НПС

4

Окрім відповіді Мартіна Сойкаса, для статичних об'єктів (або спрайтів) ви можете втекти чимось простішим.

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

Інший метод - зберегти спрайт у вигляді одноколірної форми, яка трохи більша, і зробити його раніше, ніж сам спрайт буде наданий. Це дасть вам можливість легко змінювати колір виділення, і вам може не знадобитися стільки різних кольорових форм, скільки б вам знадобилося окреслити спрайти методом №1.

І те й інше збільшить ваш слід пам’яті.


4

Як зазначається у коментарях до відповіді Мартіна Сойка, подібний ефект може бути досягнутий, використовуючи трафарет або глибинний буфер, як це детально описав Макс Макгуайр на FlipCode:

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

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

Цей підхід може бути трохи датований використанням сьогоднішнього OpenGL, і для того, щоб контур об'єкта був розмитим, рендеринг текстури все ще буде необхідним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.