3B modellerin etrafına nasıl anahat çizebilirim?


47

3B modellerin etrafına nasıl anahat çizebilirim? Son zamanlarda Pokemon oyunundaki efektler gibi bir şeyden bahsediyorum.

görüntü tanımını buraya girin görüntü tanımını buraya girin


OpenGL kullanıyor musunuz? Öyleyse, google'da OpenGL'li bir model için nasıl ana hat çizileceğini araştırmalısınız.
oxysoft

1
İçine koyduğun belirli resimlerden bahsediyorsan,% 95 kesin olarak elle çizilmiş 2B spritler olduğunu söyleyebilirim, 3D modeller değil
Panda Pajama

3
@PandaPajama: Hayır, bunlar neredeyse kesinlikle 3D modellerdir. Elle çizilmiş spritlerden beklemeyeceğim bazı karelerde sert çizgiler olması gerekenler ve her şeyden önce oyun içi 3D modellerin görünüşü bu şekilde. Sanırım bu görüntüler için% 100 garanti veremem, ama neden birisinin onları sahte göstermek için çabalayacağını tahmin edemiyorum.
CA McCann

Özellikle hangi oyun? Muhteşem gözüküyor.
Vegard

@Vegard Sırtında bir şalgam olan yaratık Pokémon oyunundan bir Bulbasaur.
Damian Yerrick

Yanıtlar:


28

Pokémon X / Y'de buradaki diğer cevapların hiçbirinin etkisini göstereceğini sanmıyorum. Nasıl yapıldığını tam olarak bilemiyorum ama oyunda yaptıkları gibi görünen bir yol buldum.

Pokémon X / Y'de, ana hatlar hem siluet kenarlarının etrafına hem de diğer siluet olmayan kenarların üzerine çizilir (Raichu'nun kulaklarının başını aşağıdaki ekran görüntüsünde bulduğu gibi).

Raichu

Raichu'nun Blender'daki ağına baktığımızda, kulağın (yukarıda turuncu ile vurgulanmış) yalnızca kafa ile kesişen ve yüzey normallerinde ani bir değişiklik yaratan, ayrı, bağlantısı kesilmiş bir nesne olduğunu görebilirsiniz.

Buna dayanarak, iki geçişte render gerektiren normları temel alan bir taslak oluşturmaya çalıştım:

İlk geçiş : Modeli (dokulu ve cel-gölgeli) ana hatları olmadan oluşturun ve kamera alanı normallerini ikinci bir oluşturma hedefine dönüştürün.

İkinci geçiş : İlk geçişten itibaren normallerin üzerinde bir tam ekran kenar algılama filtresi yapın.

Aşağıdaki ilk iki resim ilk geçişin çıktılarını göstermektedir. Üçüncüsü, kendi başına taslak ve sonuncusu nihai birleştirilmiş sonuçtur.

Dratini

İşte ikinci geçişte kenar tespiti için kullandığım OpenGL parça gölgelendiricisi. Bulabildiğim en iyi şeydi, ama daha iyi bir yol olabilir. Muhtemelen de çok iyi bir şekilde optimize edilmemiştir.

// first render target from the first pass
uniform sampler2D uTexColor;
// second render target from the first pass
uniform sampler2D uTexNormals;

uniform vec2 uResolution;

in vec2 fsInUV;

out vec4 fsOut0;

void main(void)
{
  float dx = 1.0 / uResolution.x;
  float dy = 1.0 / uResolution.y;

  vec3 center = sampleNrm( uTexNormals, vec2(0.0, 0.0) );

  // sampling just these 3 neighboring fragments keeps the outline thin.
  vec3 top = sampleNrm( uTexNormals, vec2(0.0, dy) );
  vec3 topRight = sampleNrm( uTexNormals, vec2(dx, dy) );
  vec3 right = sampleNrm( uTexNormals, vec2(dx, 0.0) );

  // the rest is pretty arbitrary, but seemed to give me the
  // best-looking results for whatever reason.

  vec3 t = center - top;
  vec3 r = center - right;
  vec3 tr = center - topRight;

  t = abs( t );
  r = abs( r );
  tr = abs( tr );

  float n;
  n = max( n, t.x );
  n = max( n, t.y );
  n = max( n, t.z );
  n = max( n, r.x );
  n = max( n, r.y );
  n = max( n, r.z );
  n = max( n, tr.x );
  n = max( n, tr.y );
  n = max( n, tr.z );

  // threshold and scale.
  n = 1.0 - clamp( clamp((n * 2.0) - 0.8, 0.0, 1.0) * 1.5, 0.0, 1.0 );

  fsOut0.rgb = texture(uTexColor, fsInUV).rgb * (0.1 + 0.9*n);
}

Ve ilk pasoyu oluşturmadan önce normallerin renderleme hedefini kameradan uzağa bakan bir vektöre yönlendiririm:

glDrawBuffer( GL_COLOR_ATTACHMENT1 );
Vec3f clearVec( 0.0, 0.0, -1.0f );
// from normalized vector to rgb color; from [-1,1] to [0,1]
clearVec = (clearVec + Vec3f(1.0f, 1.0f, 1.0f)) * 0.5f;
glClearColor( clearVec.x, clearVec.y, clearVec.z, 0.0f );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

Bir yere okudum (yorumlara bir bağlantı koyacağım) Nintendo 3DS'in gölgelendirici yerine sabit işlevli bir boru hattı kullandığını, bu yüzden oyunda tam olarak böyle olamayacağını düşünüyorum, ama şimdilik benim yöntemim yeterince yakın olduğuna ikna oldum.


Nintendo 3DS donanımı hakkında bilgiler: link
KTC

Çok güzel bir çözüm! Gölgelendiricinizdeki derinliği nasıl dikkate alırsınız? (Örneğin bir başkasının önünde bir uçak olması durumunda, her ikisi de aynı normaldir, bu yüzden hiçbir taslak çizilmez)
10

@ingham Bu durum, nadiren üstesinden gelmem gerekmeyen organik bir karakterde ortaya çıkıyor ve gerçek oyunda da ele almıyor gibi görünüyor. Gerçek oyunda, normaller aynı olduğunda bazen dış çizgilerin kaybolduğunu görebilirsiniz, ancak insanların genellikle farkına varacağını sanmıyorum.
KTC

Bunun gibi gölgelendirici bazlı tam ekran efektleri çalıştırmak için bir 3DS'nin kablolu olması konusunda biraz şüpheliyim. Gölgelendirici desteği ilkeldir (eğer varsa).
Tara

17

Bu etki, özellikle cel gölgeleme efektlerini kullanan oyunlarda yaygındır, ancak aslında cel gölgeleme stilinden bağımsız olarak uygulanabilecek bir şeydir.

Tanımladığınız şeye "özellik kenarı oluşturma" adı verilir ve genel olarak bir modelin çeşitli hatlarını ve ana hatlarını vurgulama işlemidir. Konuyla ilgili birçok teknik ve makale var.

Basit bir teknik, sadece dış kenar çizgisi olan siluet kenarını oluşturmaktır. Bu, basit bir modelin bir şablon yazı ile orijinal model haline getirilmesi ve daha sonra tekrar şablon değerinin olmadığı kalın tel kafes modunda tekrar yapılması gibi yapılabilir. Örnek bir uygulama için buraya bakınız .

Bu, iç hatlardaki konturu ve kenar çizgilerini vurgulamayacaktır (resimlerde gösterildiği gibi). Genel olarak, bunu etkin bir şekilde yapmak için, mesh'in kenarları hakkında bilgiyi çıkarmanız gerekir (kenarın iki tarafında da yüz normlarındaki süreksizliklere dayanarak) ve her bir kenarı temsil eden bir veri yapısı oluşturmalısınız.

Daha sonra, bu kenarları temel modelinize göre (veya bununla bağlantılı olarak) üste gelecek şekilde geometri olarak çıkarmak için gölgelendiriciler yazabilirsiniz. Belirli bir kenarın çizilip çizilemeyeceğini belirlemek için bir kenarın konumu ve görünüm vektörüne göre bitişik yüzlerin normalleri kullanılır.

İnternette çeşitli örneklerle daha fazla tartışma, detay ve makale bulabilirsiniz. Örneğin:


1
Stencil yönteminin (flipcode.com adresinden) çalıştığını ve gerçekten iyi göründüğünü onaylayabilirim. Kalınlığı ekran koordinatlarında verebilirsiniz, böylece anahattın kalınlığı modelin boyutuna (ya da modelin şekline) bağlı değildir.
Vegard

1
Bir teknik değil söz sıklıkla cel-gölgeleme ile birlikte kullanılan sonrası işleme sınır gölgeleme etkisi yaptığı yüksek olan pikseller için görünüm dz/dxve / veyadz/dy
bcrist

8

Bunu yapmanın en basit yolu, eski donanımda piksel / fragman gölgelendiricilerinden önceki ve hala mobilde kullanılan, modeli çoğaltmak, tepe sarım sırasını tersine çevirmek ve böylece modelin içini göstermesini sağlamaktır (veya isterseniz Bunu 3B varlık oluşturma aracınızda yapın, Blender, yüzey normallerini çevirerek - aynı şeyi yapın), sonra tüm kopyayı ortası çevresinde hafifçe genişletin ve bu kopyayı tamamen siyah / renkli hale getirin. Bu, eğer küp gibi basit bir modelse, orijinal modelinizin etrafındaki ana hatları oluşturur. İçbükey formlara sahip daha karmaşık modeller için (aşağıdaki resimde olduğu gibi), yinelenen modeli bir Minkowski Sum gibi orijinal eşdeğerinden biraz daha "daha şişman" olacak şekilde manuel olarak ayarlamak gerekir.3D Blender'ın Küçültme / Yağlama dönüşümü gibi, anahat ağını oluşturmak için her tepe noktasını normal boyunca biraz dışarı iterek başlayabilirsiniz.

Ekran boşluğu / piksel gölgelendirici yaklaşımları daha iyi uygulanması daha yavaş ve daha zor olma eğilimindedir , ancak OTOH dünyanızdaki köşe sayısını iki katına çıkarmaz. Bu yüzden eğer yüksek poli iş yapıyorsanız, bu yaklaşımı tercih edin. Geometriyi işlenmesi için modern bir konsol ve masaüstü kapasitesini göz önüne alındığında, 2 faktör dert ediyorum hiç . Karikatür tarzı = kesinlikle düşük poli, bu nedenle kopya geometrisi en kolay olanıdır.

Efekti kendiniz için örneğin herhangi bir koda dokunmadan Blender'da test edebilirsiniz. Anahatlar, aşağıdaki resme benzemelidir, iç kısımların, örneğin kolların altı gibi iç kısımlara dikkat edin. Daha fazla detay burada .

görüntü tanımını buraya girin.


1
“Tüm kopyayı merkezin etrafında hafifçe genişletin” ifadesinin bu resme nasıl uyduğunu açıklayabilir misiniz? Çünkü merkezin etrafındaki basit ölçeklendirme, eş merkezli olmayan kollar ve diğer parçalar için işe yaramaz, ayrıca herhangi bir model için işe yaramaz. içinde delikler var.
Kromster Monica

@KromStern Bazı durumlarda , tepe noktalarının alt kümelerinin barındırmak için elle ölçeklenmesi gerekir. Değiştirilmiş cevap
Mühendis

1
Normal kendi yerel yüzey boyunca dışarı tepe noktalarını itelemek için yaygın ama bu genişletilmiş anahat sert kenarlar boyunca bölünmeye örgü neden olabilir
DMGregory

Teşekkürler! Çiftleri düz düz bir renkle renklendirdiği için normları çevirmenin bir anlamı olduğunu sanmıyorum (yani normale bağlı olan süslü aydınlatma hesaplamaları yoktur). Aynı etkiyi sadece ölçeklendirerek, düz renklendirerek ve sonra kopyaların ön yüzlerini ayırarak elde ettim.
Jet Blue,

6

İçin pürüzsüz modelleri (çok önemli), bu etki oldukça basittir. Parça / piksel gölgelendiricinizde, gölgelendirilen parçanın normaline ihtiyacınız olacaktır. Eğer dikine çok yakınsa ( dot(surface_normal,view_vector) <= .01- bu eşik ile oynamanız gerekebilir), ardından parçayı normal rengi yerine siyahla renklendirin.

Bu yaklaşım, taslak oluşturmak için modelin bir kısmını "tüketir". İstediğiniz bu olabilir veya olmayabilir. Pokemon resminden bunu yapmanın bu olup olmadığını söylemek çok zor. Dış çizginin, karakterin herhangi bir siluetine dahil edilmesini beklemenize veya çerçevenin silueti içermesini tercih etmenize (farklı bir teknik gerektirir) bağlıdır.

Vurgu, "iç kenarlar" dahil (yeşil Pokemon'daki bacaklar veya kafa gibi) ön yüzden arkaya doğru geçtiği yüzeyin herhangi bir yerinde olacak - bazı diğer teknikler bunlara herhangi bir taslak eklemeyecek ).

Sert, pürüzsüz olmayan kenarlara (bir küp gibi) sahip nesneler, bu yaklaşımla istenen konumlarda vurgu elde etmez. Bu, bazı durumlarda bu yaklaşımın bir seçenek olmadığı anlamına gelir; Pokemon modellerinin hepsi düzgün olup olmadığı hakkında hiçbir fikrim yok.


5

Bunu yaptığım en yaygın yöntem, modelinize ikinci bir render geçişi yapmaktır. Temel olarak, kopyalayın ve normalleri çevirin ve bunu köşe gölgelendiricisine sürün. Gölgelendiricideki her tepe noktasını normal boyunca ölçeklendirin. Piksel / parça gölgelendiricisinde siyah çizin. Bu size dudaklar, gözler, vb. Gibi hem dış hem de iç hatlar verir. Bu, model sayısına ve karmaşıklığına bağlı olarak, çizgiyi işlemeden sonra genellikle daha ucuz bir şey değilse, aslında oldukça ucuz bir beraberlik çağrısıdır. Guilty Gear Xrd bu yöntemi kullanır, çünkü çizginin kalınlığını tepe rengi ile kontrol etmek kolaydır.

İç çizgileri yapmanın ikinci yolu aynı oyundan öğrendim. UV haritanızda, dokunuzu u veya v ekseni boyunca, özellikle de bir iç çizgi istediğiniz alanlarda hizalayın. Her iki eksen boyunca siyah bir çizgi çizin ve UV koordinatlarınızı iç çizgiyi oluşturmak için bu çizginin içine veya dışına taşıyın.

Daha iyi bir açıklama için GDC'deki videoyu izleyin: https://www.youtube.com/watch?v=yhGjCzxJV3E


5

Taslak oluşturmanın yollarından biri modellerimizi normal vektörleri kullanmaktır. Normal vektörler, yüzeylerine dik olan (yüzeyden uzağa işaret eden) vektörlerdir. Buradaki hile karakter modelinizi iki parçaya bölmektir. Kameraya bakan ve kameradan uzağa bakan köşeler. Bunları sırasıyla ÖN ve GERİ olarak adlandıracağız.

Taslak için GERİ köşelerimizi alıyoruz ve onları normal vektörleri yönünde hafifçe hareket ettiriyoruz. Karakterimizin kameradan uzağa bakan kısmını biraz daha şişmanlaştırıyor gibi düşünün. Bunu yaptıktan sonra, onlara kendi seçtiğimiz bir rengi verdik ve güzel bir taslak oluşturduk.

görüntü tanımını buraya girin

Shader "Custom/OutlineShader" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Outline("Outline Thickness", Range(0.0, 0.3)) = 0.002
        _OutlineColor("Outline Color", Color) = (0,0,0,1)
    }

    CGINCLUDE
    #include "UnityCG.cginc"

    sampler2D _MainTex;
    half4 _MainTex_ST;

    half _Outline;
    half4 _OutlineColor;

    struct appdata {
        half4 vertex : POSITION;
        half4 uv : TEXCOORD0;
        half3 normal : NORMAL;
        fixed4 color : COLOR;
    };

    struct v2f {
        half4 pos : POSITION;
        half2 uv : TEXCOORD0;
        fixed4 color : COLOR;
    };
    ENDCG

    SubShader 
    {
        Tags {
            "RenderType"="Opaque"
            "Queue" = "Transparent"
        }

        Pass{
            Name "OUTLINE"

            Cull Front

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                half3 norm = mul((half3x3)UNITY_MATRIX_IT_MV, v.normal);
                half2 offset = TransformViewToProjection(norm.xy);
                o.pos.xy += offset * o.pos.z * _Outline;
                o.color = _OutlineColor;
                return o;
            }

            fixed4 frag(v2f i) : COLOR
            {
                fixed4 o;
                o = i.color;
                return o;
            }
            ENDCG
        }

        Pass 
        {
            Name "TEXTURE"

            Cull Back
            ZWrite On
            ZTest LEqual

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.color = v.color;
                return o;
            }

            fixed4 frag(v2f i) : COLOR 
            {
                fixed4 o;
                o = tex2D(_MainTex, i.uv.xy);
                return o;
            }
            ENDCG
        }
    } 
}

Satır 41: “Cull Front” ayarı, gölgelendiriciye öne bakan köşeler üzerinde bir toplanma gerçekleştirmesini söyler. Bu, bu geçişte öne bakan tüm köşeleri görmezden geleceğimiz anlamına gelir. Biraz manipüle etmek istediğimiz GERİ taraf kaldı.

Satır 51-53: Normal vektörleri boyunca hareket eden köşelerin matematiği.

Satır 54: Köşe rengini, gölgelendirici özelliklerinde tanımlanan seçim rengimize göre ayarlama.

Faydalı Bağlantı: http://wiki.unity3d.com/index.php/Silhouette-Outlined_Diffuse


Güncelleme

başka bir örnek

görüntü tanımını buraya girin

görüntü tanımını buraya girin

   Shader "Custom/CustomOutline" {
            Properties {
                _Color ("Color", Color) = (1,1,1,1)
                _Outline ("Outline Color", Color) = (0,0,0,1)
                _MainTex ("Albedo (RGB)", 2D) = "white" {}
                _Glossiness ("Smoothness", Range(0,1)) = 0.5
                _Size ("Outline Thickness", Float) = 1.5
            }
            SubShader {
                Tags { "RenderType"="Opaque" }
                LOD 200

                // render outline

                Pass {
                Stencil {
                    Ref 1
                    Comp NotEqual
                }

                Cull Off
                ZWrite Off

                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag
                    #include "UnityCG.cginc"
                    half _Size;
                    fixed4 _Outline;
                    struct v2f {
                        float4 pos : SV_POSITION;
                    };
                    v2f vert (appdata_base v) {
                        v2f o;
                        v.vertex.xyz += v.normal * _Size;
                        o.pos = UnityObjectToClipPos (v.vertex);
                        return o;
                    }
                    half4 frag (v2f i) : SV_Target
                    {
                        return _Outline;
                    }
                    ENDCG
                }

                Tags { "RenderType"="Opaque" }
                LOD 200

                // render model

                Stencil {
                    Ref 1
                    Comp always
                    Pass replace
                }


                CGPROGRAM
                // Physically based Standard lighting model, and enable shadows on all light types
                #pragma surface surf Standard fullforwardshadows
                // Use shader model 3.0 target, to get nicer looking lighting
                #pragma target 3.0
                sampler2D _MainTex;
                struct Input {
                    float2 uv_MainTex;
                };
                half _Glossiness;
                fixed4 _Color;
                void surf (Input IN, inout SurfaceOutputStandard o) {
                    // Albedo comes from a texture tinted by color
                    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
                    o.Albedo = c.rgb;
                    // Metallic and smoothness come from slider variables
                    o.Smoothness = _Glossiness;
                    o.Alpha = c.a;
                }
                ENDCG
            }
            FallBack "Diffuse"
        }

Şablon tamponunun güncellenmiş örnekte kullanımı neden?
Tara,

Ah şimdi anladım. İkinci örnek, birincisinden farklı olarak, yalnızca dış hatları üreten bir yaklaşım kullanır. Cevabında bundan bahsetmek isteyebilirsin.
Tara

0

Bunu yapmanın en iyi yollarından biri, sahnenizi bir Framebuffer dokusu üzerinde, ardından o dokuyu , her piksel üzerinde bir Sobel Filtresi yaparken , bu da kenar algılama için kolay bir teknik haline getirmektir. Bu şekilde sadece sahneyi pikselleştiremezsiniz (Framebuffer dokusuna düşük bir çözünürlük ayarlayarak) değil, aynı zamanda Sobel'i çalıştırmak için her piksel değerine erişebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.