menguraikan efek objek

26

Bagaimana saya bisa mencapai efek garis besar yang serupa dengan yang ditemukan di League of Legends atau Diablo III?

Garis besar League of Legends Garis besar League of Legends Garis besar Diablo III

Apakah sudah selesai menggunakan shader? Bagaimana?
Saya lebih suka jawaban yang tidak terikat pada mesin tertentu tetapi yang saya bisa beradaptasi dengan mesin apa pun yang saya kerjakan.

João Portela
sumber

Jawaban:

19

Anda akan harus membuat objek dua kali di beberapa titik. Anda dapat pergi dengan rendering hanya wajah yang menghadap kamera sekali dan wajah yang menghadap jauh dari kamera sekali, tetapi memiliki trade-off.

Solusi umum yang paling sederhana dilakukan dengan merender objek dua kali dalam pass yang sama:

  • Anda menggunakan vertex shader untuk membalikkan normals dari objek dan "meledakkannya" dengan ukuran outline dan shader fragmen untuk membuatnya dalam warna outline
  • Lebih dari garis itu membuat, Anda membuat objek secara normal. Z-order biasanya secara otomatis benar, lebih atau kurang, karena garis dibuat oleh wajah-wajah yang ada di "belakang" objek sementara gambar itu sendiri terdiri dari wajah-wajah yang menghadap kamera.

Ini cukup sederhana untuk membangun dan mengimplementasikan dan menghindari trik render-to-tekstur, tetapi memiliki beberapa kelemahan nyata:

  • Ukuran garis besar, jika Anda tidak mengukurnya berdasarkan jarak dari kamera, akan bervariasi. Objek yang lebih jauh akan memiliki garis besar yang lebih kecil daripada yang terdekat. Tentu saja, ini mungkin yang sebenarnya Anda inginkan .
  • Vertex shader "blow up" tidak bekerja dengan baik untuk objek kompleks seperti kerangka dalam contoh Anda, dengan mudah memperkenalkan artefak z-fight ke render. Memperbaikinya mengharuskan Anda untuk membuat objek dalam dua lintasan, tetapi menyelamatkan Anda dari membalikkan normals.
  • Garis besar dan objek mungkin tidak bekerja dengan baik ketika objek lain menempati ruang yang sama dan secara umum sulit untuk diperbaiki ketika dikombinasikan dengan bayangan dan pembiasan pembiasan.

Gagasan dasar untuk shader seperti ini terlihat seperti ini (Cg, untuk Unity - kodenya adalah sedikit dimodifikasi toon shader yang saya temukan di suatu tempat dan tidak mencatat sumbernya, jadi ini adalah bukti konsep yang lebih buruk daripada konsep siap-pakai). untuk menggunakan shader):

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

Metode umum lainnya membuat objek dua kali juga, tetapi menghindari vertex shader sepenuhnya. Di sisi lain, itu tidak dapat dengan mudah dilakukan dalam satu lintasan, dan membutuhkan render-to-tekstur: Membuat objek sekali dengan "datar" garis-garis shader fragmen berwarna dan menggunakan blur (tertimbang) pada render ini di layar ruang , lalu render objek seperti biasa di atasnya.

Ada juga metode ketiga dan mungkin paling mudah untuk diterapkan, meskipun pajak GPU sedikit lebih dan akan membuat artis Anda ingin membunuh Anda dalam tidur Anda kecuali Anda membuatnya mudah bagi mereka untuk menghasilkan: Buat benda memiliki garis besar sebagai terpisah jala sepanjang waktu, hanya sepenuhnya transparan atau pindah ke suatu tempat di mana itu tidak terlihat (seperti jauh di bawah tanah) sampai Anda membutuhkannya

Martin Sojka
sumber
Apakah buffer stensil bukan pendekatan yang umum digunakan di sini?
edA-qa mort-ora-y
1
@ edA-qamort-ora-y: Ini bisa bekerja juga, tapi saya tidak pernah mencoba pendekatan itu, jadi tidak bisa mengomentarinya. :) Jika Anda memiliki algoritma yang berfungsi, jangan ragu untuk menambahkan metode ini sebagai jawaban lain juga.
Martin Sojka
Saya sendiri tidak tahu lebih banyak tentang itu, hanya garis yang sering disebutkan sehubungan dengan buffer stensil. :) Sepertinya itu mungkin versi yang lebih berbasis hardware dari pendekatan pertama Anda (dua pass, pertama lebih besar).
edA-qa mort-ora-y
Pendekatan buffer stensil dapat menggunakan lebih banyak bandwidth dalam memperbarui dan menghapus buffer stensil, dan membutuhkan beberapa lintasan. Pendekatan daftar Martin dapat dilakukan dalam satu lulus dalam beberapa kasus terbatas, paling banyak dua, dan memerlukan overhead bandwidth minimal.
Sean Middleditch
Pendekatan ini (meledakkan ukuran sepanjang normals) tidak berfungsi dengan objek seperti kubus (terutama dengan kamera orto). Ada solusi untuk itu?
NPS
4

Selain jawaban Martin Sojkas, untuk objek statis (atau sprite), Anda dapat menggunakan sesuatu yang lebih sederhana.

Anda dapat menyimpan sprite yang sama tetapi dengan garis besar ke dalam atlas tekstur Anda atau seluruhnya tekstur lain, yang membuat beralih mudah. Ini juga akan memungkinkan Anda untuk melakukan garis besar khusus yang lebih menarik secara visual atau hanya terlihat berbeda.

Metode lainnya adalah menyimpan sprite sebagai bentuk satu warna yang sedikit lebih besar dan merender sebelum sprite itu sendiri dibuat. Ini akan memberi Anda kemampuan untuk dengan mudah mengubah warna seleksi, dan Anda mungkin tidak membutuhkan bentuk warna yang jauh berbeda karena Anda akan perlu menguraikan sprite dengan metode # 1.

Keduanya akan meningkatkan jejak memori Anda.

Darcara
sumber
4

Seperti yang ditunjukkan dalam komentar untuk jawaban Martin Sojka, efek serupa juga dapat dicapai dengan menggunakan stensil atau penyangga kedalaman, seperti yang dirinci oleh Max McGuire pada FlipCode:

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

Ini pada dasarnya menggambar versi wireframe dari model yang Anda inginkan diuraikan dengan peningkatan lebar garis (atau dalam kasus ini tidak mungkin, seperti dalam D3D misalnya, menggunakan kamera menghadap paha depan untuk garis) sambil mengatur buffer stensil ke nilai konstan.

Aproach ini mungkin sedikit tanggal menggunakan OpenGL hari ini dan agar objek garis apel kabur, rendering ke tekstur masih akan diperlukan.

Koarl
sumber